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;
}
}