# PDSeedValueCertificate.java

    /*
     * Copyright 2017 The Apache Software Foundation.
     *
     * 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 org.apache.pdfbox.pdmodel.interactive.digitalsignature;

    import java.util.HashMap;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.Map;
    import org.apache.pdfbox.cos.COSArray;
    import org.apache.pdfbox.cos.COSBase;
    import org.apache.pdfbox.cos.COSDictionary;
    import org.apache.pdfbox.cos.COSName;
    import org.apache.pdfbox.cos.COSString;
    import org.apache.pdfbox.pdmodel.common.COSObjectable;

    /**
     * This class represents a certificate seed value dictionary that is in the seed value which puts
     * constraints on certificates when signing documents.
     *
     * @author Hossam Hazem
     */
    public class PDSeedValueCertificate implements COSObjectable
    {
        /**
         * A Ff flag.
         */
        public static final int FLAG_SUBJECT = 1;

        /**
         * A Ff flag.
         */
        public static final int FLAG_ISSUER = 1 << 1;

        /**
         * A Ff flag.
         */
        public static final int FLAG_OID = 1 << 2;

        /**
         * A Ff flag.
         */
        public static final int FLAG_SUBJECT_DN = 1 << 3;

        /**
         * A Ff flag.
         */
        public static final int FLAG_KEY_USAGE = 1 << 5;

        /**
         * A Ff flag.
         */
        public static final int FLAG_URL = 1 << 6;
        private final COSDictionary dictionary;

        /**
         * Default constructor.
         */
        public PDSeedValueCertificate()
        {
            dictionary = new COSDictionary();
            dictionary.setItem(COSName.TYPE, COSName.SV_CERT);
            dictionary.setDirect(true);
        }

        /**
         * Constructor.
         *
         * @param dict The certificate seed value dictionary.
         */
        public PDSeedValueCertificate(COSDictionary dict)
        {
            dictionary = dict;
            dictionary.setDirect(true);
        }

        /**
         * Convert this standard java object to a COS dictionary.
         *
         * @return The COS dictionary that matches this Java object.
         */
        @Override
        public COSDictionary getCOSObject()
        {
            return dictionary;
        }

        /**
         *
         * @return true if the Subject is required
         */
        public boolean isSubjectRequired()
        {
            return dictionary.getFlag(COSName.FF, FLAG_SUBJECT);
        }

        /**
         * set true if subject shall be required as a constraint on signature.
         *
         * @param flag if true, the specified Subject shall be enforced as a constraint.
         */
        public void setSubjectRequired(boolean flag)
        {
            dictionary.setFlag(COSName.FF, FLAG_SUBJECT, flag);
        }

        /**
         *
         * @return true if the Issuer is required
         */
        public boolean isIssuerRequired()
        {
            return dictionary.getFlag(COSName.FF, FLAG_ISSUER);
        }

        /**
         * set true if Issuer shall be required as a constraint on signature.
         *
         * @param flag if true, the specified Issuer shall be enforced as a constraint.
         */
        public void setIssuerRequired(boolean flag)
        {
            dictionary.setFlag(COSName.FF, FLAG_ISSUER, flag);
        }

        /**
         *
         * @return true if the OID is required
         */
        public boolean isOIDRequired()
        {
            return dictionary.getFlag(COSName.FF, FLAG_OID);
        }

        /**
         * set true if OID shall be required as a constraint on signature.
         *
         * @param flag if true, the specified OID shall be enforced as a constraint.
         */
        public void setOIDRequired(boolean flag)
        {
            dictionary.setFlag(COSName.FF, FLAG_OID, flag);
        }

        /**
         *
         * @return true if the Subject DN is required
         */
        public boolean isSubjectDNRequired()
        {
            return dictionary.getFlag(COSName.FF, FLAG_SUBJECT_DN);
        }

        /**
         * set true if subject DN shall be required as a constraint on signature.
         *
         * @param flag if true, the specified Subject DN shall be enforced as a constraint.
         */
        public void setSubjectDNRequired(boolean flag)
        {
            dictionary.setFlag(COSName.FF, FLAG_SUBJECT_DN, flag);
        }

        /**
         *
         * @return true if the KeyUsage is required
         */
        public boolean isKeyUsageRequired()
        {
            return dictionary.getFlag(COSName.FF, FLAG_KEY_USAGE);
        }

        /**
         * set true if KeyUsage shall be required as a constraint on signature.
         *
         * @param flag if true, the specified KeyUsage shall be enforced as a constraint.
         */
        public void setKeyUsageRequired(boolean flag)
        {
            dictionary.setFlag(COSName.FF, FLAG_KEY_USAGE, flag);
        }

        /**
         *
         * @return true if the URL is required
         */
        public boolean isURLRequired()
        {
            return dictionary.getFlag(COSName.FF, FLAG_URL);
        }

        /**
         * set true if URL shall be required as a constraint on signature.
         *
         * @param flag if true, the specified URL shall be enforced as a constraint.
         */
        public void setURLRequired(boolean flag)
        {
            dictionary.setFlag(COSName.FF, FLAG_URL, flag);
        }

        /**
         * Returns list of byte arrays that contains DER-encoded X.509v3 certificates
         */
        public List<byte[]> getSubject()
        {
            COSArray array = dictionary.getCOSArray(COSName.SUBJECT);
            return array != null ? getListOfByteArraysFromCOSArray(array) : null;
        }

        /**
         * (Optional) A list of byte arrays containing DER-encoded X.509v3 certificates that are
         * acceptable for signing. if
         * <b>Subject</b> is not null and {@link #isSubjectRequired()} is true then the subject
         * constraint is enforced on the subjects in this array subjects.
         *
         * @param subjects list of byte arrays containing DER-encoded X.509v3 certificates that are
         * acceptable for signing.
         */
        public void setSubject(List<byte[]> subjects)
        {
            dictionary.setItem(COSName.SUBJECT, convertListOfByteArraysToCOSArray(subjects));
        }

        /**
         * (Optional) byte array containing DER-encoded X.509v3 certificate that is acceptable for
         * signing. works like {@link #setSubject(List)} but one byte array
         *
         * @param subject byte array containing DER-encoded X.509v3 certificate
         */
        public void addSubject(byte[] subject)
        {
            COSArray array = dictionary.getCOSArray(COSName.SUBJECT);
            if (array == null)
            {
                array = new COSArray();
            }
            array.add(new COSString(subject));
            dictionary.setItem(COSName.SUBJECT, array);
        }

        /**
         * removes a subject from the list
         *
         * @param subject byte array containing DER-encoded X.509v3 certificate
         */
        public void removeSubject(byte[] subject)
        {
            COSArray array = dictionary.getCOSArray(COSName.SUBJECT);
            if (array != null)
            {
                array.remove(new COSString(subject));
            }
        }

        /**
         * Returns list of maps that contains subject distinguished names like [(cn: John Doe, o: Doe),
         * (cn: John Smith)] both keys are typically of the form 'cn', 'o', 'email', '2.5.4.43'; and
         * values are text strings.
         */
        public List<Map<String, String>> getSubjectDN()
        {
            COSArray cosArray = dictionary.getCOSArray(COSName.SUBJECT_DN);
            if (cosArray != null)
            {
                List<? extends COSBase> subjectDNList = cosArray.toList();
                List<Map<String, String>> result = new LinkedList<>();
                for (COSBase subjectDNItem : subjectDNList)
                {
                    if (subjectDNItem instanceof COSDictionary)
                    {
                        COSDictionary subjectDNItemDict = (COSDictionary) subjectDNItem;
                        Map<String, String> subjectDNMap = new HashMap<>();
                        for (COSName key : subjectDNItemDict.keySet())
                        {
                            subjectDNMap.put(key.getName(), subjectDNItemDict.getString(key));
                        }
                        result.add(subjectDNMap);
                    }
                }
                return result;
            }
            return null;
        }

        /**
         * (Optional; PDF 1.7) A list of maps, where each map contains key value pairs, that specify the
         * Subject Distinguished Name (DN) that must be present within the certificate for it to be
         * acceptable for signing. The certificate must at a minimum contain all the attributes
         * specified in one of the maps entered.
         *
         * @param subjectDN list of maps that contains subject distinguished names
         */
        public void setSubjectDN(List<Map<String, String>> subjectDN)
        {
            List<COSDictionary> subjectDNDict = new LinkedList<>();
            for (Map<String, String> subjectDNItem : subjectDN)
            {
                COSDictionary dict = new COSDictionary();
                subjectDNItem.forEach((key, value) -> dict.setItem(key, new COSString(value)));
                subjectDNDict.add(dict);
            }
            dictionary.setItem(COSName.SUBJECT_DN, new COSArray(subjectDNDict));
        }

        /**
         * Returns list of key usages of certificate strings where each string is 9 characters long and
         * each character is one of these values {0, 1, X} 0 for must not set, 1 for must set, X for
         * don't care. each index in the string represents a key usage:
         * <ol>
         * <li>digitalSignature</li>
         * <li>non-Repudiation</li>
         * <li>keyEncipherment</li>
         * <li>dataEncipherment</li>
         * <li>keyAgreement</li>
         * <li>keyCertSign</li>
         * <li>cRLSign</li>
         * <li>encipherOnly</li>
         * <li>decipherOnly</li>
         * </ol>
         */
        public List<String> getKeyUsage()
        {
            COSArray array = dictionary.getCOSArray(COSName.KEY_USAGE);
            if (array != null)
            {
                List<String> keyUsageExtensions = new LinkedList<>();
                for (COSBase item : array)
                {
                    if (item instanceof COSString)
                    {
                        keyUsageExtensions.add(((COSString) item).getString());
                    }
                }
                return keyUsageExtensions;
            }
            return null;
        }

        /**
         * (Optional; PDF 1.7) A List of ASCII strings, where each string specifies an acceptable
         * key-usage extension that must be present in the signing certificate. Multiple strings specify
         * a range of acceptable key-usage extensions; where each string 9 characters long and each
         * character is one of these values {0, 1, X} 0 for must not set, 1 for must set, X for don't
         * care. each index in the string represents a key usage:
         * <ol>
         * <li>digitalSignature</li>
         * <li>non-Repudiation</li>
         * <li>keyEncipherment</li>
         * <li>dataEncipherment</li>
         * <li>keyAgreement</li>
         * <li>keyCertSign</li>
         * <li>cRLSign</li>
         * <li>encipherOnly</li>
         * <li>decipherOnly</li>
         * </ol>
         *
         * @param keyUsageExtensions list of ASCII strings that consists only of {0, 1, X}
         */
        public void setKeyUsage(List<String> keyUsageExtensions)
        {
            dictionary.setItem(COSName.KEY_USAGE,
                    COSArray.ofCOSStrings(keyUsageExtensions));
        }

        /**
         * (Optional; PDF 1.7) specifies an acceptable key-usage extension that must be presennt in the
         * signing certificate for works like {@link #setKeyUsage(List)} but takes only one string
         *
         * @param keyUsageExtension String that consist only of {0, 1, X}
         */
        public void addKeyUsage(String keyUsageExtension)
        {
            String allowedChars = "01X";
            for (int c = 0; c < keyUsageExtension.length(); c++)
            {
                if (allowedChars.indexOf(keyUsageExtension.charAt(c)) == -1)
                {
                    throw new IllegalArgumentException("characters can only be 0, 1, X");
                }
            }
            COSArray array = dictionary.getCOSArray(COSName.KEY_USAGE);
            if (array == null)
            {
                array = new COSArray();
            }
            array.add(new COSString(keyUsageExtension));
            dictionary.setItem(COSName.KEY_USAGE, array);
        }

        /**
         * works like {@link #addKeyUsage(String)} but enters each character separately
         *
         * @param digitalSignature char that is one of {0, 1, X}
         * @param nonRepudiation char that is one of {0, 1, X}
         * @param keyEncipherment char that is one of {0, 1, X}
         * @param dataEncipherment char that is one of {0, 1, X}
         * @param keyAgreement char that is one of {0, 1, X}
         * @param keyCertSign char that is one of {0, 1, X}
         * @param cRLSign char that is one of {0, 1, X}
         * @param encipherOnly char that is one of {0, 1, X}
         * @param decipherOnly char that is one of {0, 1, X}
         */
        public void addKeyUsage(char digitalSignature, char nonRepudiation, char keyEncipherment,
                char dataEncipherment, char keyAgreement, char keyCertSign, char cRLSign,
                char encipherOnly, char decipherOnly)
        {
            StringBuilder builder = new StringBuilder();
            builder.append(digitalSignature);
            builder.append(nonRepudiation);
            builder.append(keyEncipherment);
            builder.append(dataEncipherment);
            builder.append(keyAgreement);
            builder.append(keyCertSign);
            builder.append(cRLSign);
            builder.append(encipherOnly);
            builder.append(decipherOnly);

            addKeyUsage(builder.toString());
        }

        /**
         * Removes a key usage extension
         *
         * @param keyUsageExtension ASCII string that consists of {0, 1, X}
         */
        public void removeKeyUsage(String keyUsageExtension)
        {
            COSArray array = dictionary.getCOSArray(COSName.KEY_USAGE);
            if (array != null)
            {
                array.remove(new COSString(keyUsageExtension));
            }
        }

        /**
         * Returns list of array of bytes of DER-encoded X.509v3 certificates
         */
        public List<byte[]> getIssuer()
        {
            COSArray array = dictionary.getCOSArray(COSName.ISSUER);
            return array != null ? getListOfByteArraysFromCOSArray(array) : null;
        }

        /**
         * (Optional) A list of array of bytes containing DER-encoded X.509v3 certificates of acceptable
         * issuers. If the signer’s certificate chains up to any of the specified issuers (either
         * directly or indirectly), the certificate is considered acceptable for signing.
         *
         * @param issuers A list of byte array containing DER-encoded X.509v3 certificates
         */
        public void setIssuer(List<byte[]> issuers)
        {
            dictionary.setItem(COSName.ISSUER, convertListOfByteArraysToCOSArray(issuers));
        }

        /**
         * array of bytes containing DER-encoded X.509v3 certificates of acceptable issuers. If the
         * signer’s certificate chains up to any of the specified issuers (either directly or
         * indirectly), the certificate is considered acceptable for signing.
         *
         * @param issuer A byte array containing DER-encoded X.509v3 certificate
         */
        public void addIssuer(byte[] issuer)
        {
            COSArray array = dictionary.getCOSArray(COSName.ISSUER);
            if (array == null)
            {
                array = new COSArray();
            }
            array.add(new COSString(issuer));
            dictionary.setItem(COSName.ISSUER, array);
        }

        /**
         * Removes an issuer from the issuers list
         *
         * @param issuer A byte array containing DER-encoded X.509v3 certificate
         */
        public void removeIssuer(byte[] issuer)
        {
            COSArray array = dictionary.getCOSArray(COSName.ISSUER);
            if (array != null)
            {
                array.remove(new COSString(issuer));
            }
        }

        /**
         * Returns A list of array of bytes that contain Object Identifiers (OIDs) of the certificate
         * policies that must be present in the signing certificate
         */
        public List<byte[]> getOID()
        {
            COSArray array = dictionary.getCOSArray(COSName.OID);
            return array != null ? getListOfByteArraysFromCOSArray(array) : null;
        }

        /**
         * (Optional) A list of byte arrays that contain Object Identifiers (OIDs) of the certificate
         * policies that must be present in the signing certificate. This field is only applicable if
         * the value of Issuer is not empty.
         *
         * @param oidByteStrings list of byte arrays that contain OIDs
         */
        public void setOID(List<byte[]> oidByteStrings)
        {
            dictionary.setItem(COSName.OID, convertListOfByteArraysToCOSArray(oidByteStrings));
        }

        /**
         * works like {@link #setOID(List)} but for one object
         *
         * @param oid
         */
        public void addOID(byte[] oid)
        {
            COSArray array = dictionary.getCOSArray(COSName.OID);
            if (array == null)
            {
                array = new COSArray();
            }
            array.add(new COSString(oid));
            dictionary.setItem(COSName.OID, array);
        }

        /**
         * removes an OID from the list
         *
         * @param oid
         */
        public void removeOID(byte[] oid)
        {
            COSArray array = dictionary.getCOSArray(COSName.OID);
            if (array != null)
            {
                array.remove(new COSString(oid));
            }
        }

        /**
         * returns String of the URL
         */
        public String getURL()
        {
            return dictionary.getString(COSName.URL);
        }

        /**
         * (Optional) A URL, the use for which is defined by the URLType entry.
         *
         * @param url String of the URL
         */
        public void setURL(String url)
        {
            dictionary.setString(COSName.URL, url);
        }

        /**
         * A name indicating the usage of the URL entry. There are standard uses and there can be
         * implementation-specific use for this URL. The following value specifies a valid standard
         * usage:
         * <ul>
         * <li>Browser, The URL references content that should be displayed in a web browser to allow
         * enrolling for a new credential if a matching credential is not found. The Ff attribute’s URL
         * bit is ignored for this usage.</li>
         * <li>ASSP, The URL references a signature web service that can be used for server-based
         * signing. If the Ff attribute’s URL bit indicates that this is a required constraint, this
         * implies that the credential used when signing must come from this server.</li>
         * </ul>
         *
         * @return string of URL type
         */
        public String getURLType()
        {
            return dictionary.getNameAsString(COSName.URL_TYPE);
        }

        /**
         * (Optional; PDF 1.7) A name indicating the usage of the URL entry. There are standard uses and
         * there can be implementation-specific uses for this URL. The following value specifies a valid
         * standard usage:
         * <ul>
         * <li>Browser, The URL references content that should be displayed in a web browser to allow
         * enrolling for a new credential if a matching credential is not found. The Ff attribute’s URL
         * bit is ignored for this usage.</li>
         * <li>ASSP, The URL references a signature web service that can be used for server-based
         * signing. If the Ff attribute’s URL bit indicates that this is a required constraint, this
         * implies that the credential used when signing must come from this server.</li>
         * </ul>
         * Third parties can extend the use of this attribute with their own attribute values, which
         * must conform to the guidelines specified in
         * <a href="http://www.adobe.com/content/dam/acom/en/devnet/pdf/PDF32000_2008.pdf#page=681">PDF
         * Spec 1.7 Appendix E (PDF Name Registry)</a>
         * if urlType is not set the default is Browser for URL
         *
         * @param urlType String of the urlType
         */
        public void setURLType(String urlType)
        {
            dictionary.setName(COSName.URL_TYPE, urlType);
        }

        private static List<byte[]> getListOfByteArraysFromCOSArray(COSArray array)
        {
            List<byte[]> result = new LinkedList<>();
            for (COSBase item : array)
            {
                if (item instanceof COSString)
                {
                    result.add(((COSString) item).getBytes());
                }
            }
            return result;
        }

        private static COSArray convertListOfByteArraysToCOSArray(List<byte[]> strings)
        {
            COSArray array = new COSArray();
            strings.forEach(s -> array.add(new COSString(s)));
            return array;
        }
    }