/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * 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.
 */
/*
 * Copyright (c) 2015-2017, The Linux Foundation.
 */

/*
 * Copyright (C) 2011 Deutsche Telekom, A.G.
 *
 * 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.
 */

/*
 * Contributed by: Giesecke & Devrient GmbH.
 */

package com.android.se.security.arf.pkcs15;

import android.util.Log;

import com.android.se.internal.Util;
import com.android.se.security.ApduFilter;
import com.android.se.security.ChannelAccess;
import com.android.se.security.arf.ASN1;
import com.android.se.security.arf.DERParser;
import com.android.se.security.arf.SecureElement;
import com.android.se.security.gpac.AID_REF_DO;
import com.android.se.security.gpac.Hash_REF_DO;

import java.io.IOException;
import java.util.Vector;

/** EF_ACConditions related features ************************************************* */
public class EFACConditions extends EF {

    public static final String TAG = "ACE ARF EF_ACConditions";

    // Identification of the cardlet
    private AID_REF_DO mAidRefDo = null;

    private byte[] mData = null;

    /**
     * Constructor
     *
     * @param secureElement SE on which ISO7816 commands are applied
     * @param AID           Identification of the applet
     */
    public EFACConditions(SecureElement handle, AID_REF_DO aidRefDo) {
        super(handle);
        mAidRefDo = aidRefDo;
    }

    /**
     * Decodes EF_ACConditions file
     *
     * @param buffer ASN.1 data
     */
    private void decodeDER(byte[] buffer) throws PKCS15Exception {
        byte[] certificateHash = null;
        DERParser der = new DERParser(buffer);

        // the default channelAccess will deny every access.
        ChannelAccess channelAccess = new ChannelAccess();
        Hash_REF_DO hash_ref_do = new Hash_REF_DO();

        // empty condition file
        if (der.isEndofBuffer()) {
            channelAccess.setAccess(ChannelAccess.ACCESS.DENIED, "Empty ACCondition");
            channelAccess.setNFCEventAccess(ChannelAccess.ACCESS.DENIED);
            channelAccess.setApduAccess(ChannelAccess.ACCESS.DENIED);
            channelAccess.setUseApduFilter(false);
            channelAccess.setApduFilter(null);
            Log.i(TAG, "Empty ACCondition: Access Deny for all apps");

            mSEHandle.putAccessRule(mAidRefDo, hash_ref_do, channelAccess);
            return;
        }

        // ----
        // 2012-04-16
    /*
      Condition ::= SEQUENCE {
             cert   CertHash OPTIONAL,
             accessRules    [0]AccessRules OPTIONAL
       }

       AccessRules ::= SEQUENCE OF AccessRule

       AccessRule ::=CHOICE {
           apduAccessRule    [0]APDUAccessRule,
           nfcAccessRule    [1]NFCAccessRule
       }

       APDUAccessRule ::= CHOICE {
              apduPermission [0] APDUPermission,
              apduFilter [1] APDUFilter
       }

       APDUFilters ::= SEQUENCE OF APDUFilter

       NFCAccessRule ::= CHOICE {
              nfcPermission [0] NFCPermission
       }
    */
        while (!der.isEndofBuffer()) {

            // if a hash value was found then access is allowed
            // even if NO more access rule is given.
            // missing APDU Permission will always allow APDU access
            // missing NFC Permission will always allow NFC event.
            // See GPAC Chapter 7.1.7
            // See Examples in Annex C of GPAC
            channelAccess = new ChannelAccess();
            channelAccess.setAccess(ChannelAccess.ACCESS.ALLOWED, "");
            channelAccess.setApduAccess(ChannelAccess.ACCESS.ALLOWED);
            channelAccess.setNFCEventAccess(ChannelAccess.ACCESS.UNDEFINED);
            channelAccess.setUseApduFilter(false);

            if (der.parseTLV(ASN1.TAG_Sequence) > 0) {
                byte[] tempTLVData = der.getTLVData();
                DERParser derRule = new DERParser(tempTLVData);

                if (tempTLVData[0] == ASN1.TAG_OctetString) {
                    derRule.parseTLV(ASN1.TAG_OctetString);
                    certificateHash = derRule.getTLVData();

                    if (certificateHash.length != Hash_REF_DO.SHA1_LEN
                            && certificateHash.length != Hash_REF_DO.SHA256_LEN
                            && certificateHash.length != 0) {
                        // other hash than SHA-1 and SHA-256 hash values are not supported.
                        throw new PKCS15Exception("Invalid hash found!");
                    } else {
                        hash_ref_do = new Hash_REF_DO(certificateHash);
                    }
                } else if (tempTLVData[0] == ASN1.TAG_Padding) {
                    throw new PKCS15Exception("Invalid hash found!");
                } else {
                    Log.i(TAG, "No hash included");
                    // Let's put a null hash, to prioritize any more specific rule.
                    hash_ref_do = new Hash_REF_DO(null);
                }

                // 2012-04-16
                // parse optional Access Rule.
                if (!derRule.isEndofBuffer()) {

                    if (derRule.parseTLV() == (byte) 0xA0) {

                        DERParser derAccessRules = new DERParser(derRule.getTLVData());

                        while (!derAccessRules.isEndofBuffer()) {
                            switch (derAccessRules.parseTLV()) {
                                // APDU Access Rule
                                case (byte) 0xA0:
                                    DERParser derApduRule = new DERParser(
                                            derAccessRules.getTLVData());
                                    byte tagApduAccessRule = derApduRule.parseTLV();

                                    if (tagApduAccessRule
                                            == (byte) 0x80) { // APDU Permission  (primitive)

                                        channelAccess.setApduAccess(
                                                derApduRule.getTLVData()[0] == 0x01
                                                        ? ChannelAccess.ACCESS.ALLOWED
                                                        : ChannelAccess.ACCESS.DENIED);

                                    } else if (tagApduAccessRule
                                            == (byte) 0xA1) { // APDU Filter (constructed)

                                        DERParser derApduFilter = new DERParser(
                                                derApduRule.getTLVData());
                                        byte tag = derApduFilter.parseTLV();

                                        if (tag == ASN1.TAG_OctetString) {

                                            Vector<ApduFilter> apduFilter =
                                                    new Vector<ApduFilter>();

                                            // collect all apdu filter tlvs.
                                            apduFilter.add(
                                                    new ApduFilter(derApduFilter.getTLVData()));

                                            while (!derApduFilter.isEndofBuffer()) {
                                                if (derApduFilter.parseTLV()
                                                        == ASN1.TAG_OctetString) {
                                                    apduFilter.add(new ApduFilter(
                                                            derApduFilter.getTLVData()));
                                                }
                                            }
                                            channelAccess.setUseApduFilter(true);
                                            channelAccess.setApduFilter(
                                                    apduFilter.toArray(
                                                            new ApduFilter[apduFilter.size()]));
                                        } else {
                                            throw new PKCS15Exception("Invalid element found!");
                                        }

                                    } else {
                                        throw new PKCS15Exception("Invalid element found!");
                                    }
                                    break;
                                // NFC Access Rule
                                case (byte) 0xA1:
                                    DERParser derNfc = new DERParser(derAccessRules.getTLVData());

                                    if (derNfc.parseTLV()
                                            == (byte) 0x80) { // NFC Permission (primitive)
                                        channelAccess.setNFCEventAccess(
                                                derNfc.getTLVData()[0] == (byte) 0x01
                                                        ? ChannelAccess.ACCESS.ALLOWED
                                                        : ChannelAccess.ACCESS.DENIED);
                                    } else {
                                        throw new PKCS15Exception("Invalid element found!");
                                    }
                                    break;
                                default:
                                    throw new PKCS15Exception("Invalid element found!");
                            }
                        }
                    } else {
                        // no explicit access rule given.
                    }
                }
            } else {
                // coding 30 00 -> empty hash value given (all applications)
                if ((channelAccess.getNFCEventAccess() == ChannelAccess.ACCESS.UNDEFINED)
                        && (channelAccess.getApduAccess() != ChannelAccess.ACCESS.UNDEFINED)) {
                    channelAccess.setNFCEventAccess(channelAccess.getApduAccess());
                }
            }
            // ----
            mSEHandle.putAccessRule(mAidRefDo, hash_ref_do, channelAccess);
        }
    }

    /**
     * Stores a restricted list of certificate hashes
     *
     * @param path Path of the "EF_ACConditions" file
     */
    public void addRestrictedHashes(byte[] path) throws IOException, PKCS15Exception {
        try {
            Log.i(TAG, "Reading and analysing EF_ACConditions...");
            if (selectFile(path) == APDU_SUCCESS) {
                mData = readBinary(0, Util.END);
                decodeDER(mData);
            } else {
                Log.e(TAG, "EF_ACConditions not found!");
            }
        } catch (Exception e) {
            throw new PKCS15Exception(e.getMessage());
        }
    }

    /**
     * Stores a restricted list of certificate hashes
     */
    public void addRestrictedHashesFromData(byte[] data) throws PKCS15Exception {
        try {
            Log.i(TAG, "Analysing cached EF_ACConditions data...");
            if (data != null) {
                mData = data;
                decodeDER(mData);
            } else {
                Log.e(TAG, "EF_ACConditions data not available!");
            }
        } catch (Exception e) {
            throw new PKCS15Exception(e.getMessage());
        }
    }

    public byte[] getData() {
        return mData;
    }
}
