1package org.bouncycastle.x509;
2
3import org.bouncycastle.asn1.ASN1Encodable;
4import org.bouncycastle.asn1.ASN1Sequence;
5import org.bouncycastle.asn1.DERInteger;
6import org.bouncycastle.asn1.DERSequence;
7import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
8import org.bouncycastle.asn1.x509.GeneralName;
9import org.bouncycastle.asn1.x509.GeneralNames;
10import org.bouncycastle.asn1.x509.Holder;
11import org.bouncycastle.asn1.x509.IssuerSerial;
12import org.bouncycastle.asn1.x509.ObjectDigestInfo;
13import org.bouncycastle.jce.PrincipalUtil;
14import org.bouncycastle.jce.X509Principal;
15import org.bouncycastle.util.Arrays;
16import org.bouncycastle.util.Selector;
17
18import javax.security.auth.x500.X500Principal;
19import java.io.IOException;
20import java.math.BigInteger;
21import java.security.MessageDigest;
22import java.security.Principal;
23import java.security.cert.CertSelector;
24import java.security.cert.Certificate;
25import java.security.cert.CertificateEncodingException;
26import java.security.cert.CertificateParsingException;
27import java.security.cert.X509Certificate;
28import java.util.ArrayList;
29import java.util.List;
30
31/**
32 * The Holder object.
33 *
34 * <pre>
35 *          Holder ::= SEQUENCE {
36 *                baseCertificateID   [0] IssuerSerial OPTIONAL,
37 *                         -- the issuer and serial number of
38 *                         -- the holder's Public Key Certificate
39 *                entityName          [1] GeneralNames OPTIONAL,
40 *                         -- the name of the claimant or role
41 *                objectDigestInfo    [2] ObjectDigestInfo OPTIONAL
42 *                         -- used to directly authenticate the holder,
43 *                         -- for example, an executable
44 *          }
45 * </pre>
46 *
47 */
48public class AttributeCertificateHolder
49    implements CertSelector, Selector
50{
51    final Holder holder;
52
53    AttributeCertificateHolder(ASN1Sequence seq)
54    {
55        holder = Holder.getInstance(seq);
56    }
57
58    public AttributeCertificateHolder(X509Principal issuerName,
59        BigInteger serialNumber)
60    {
61        holder = new org.bouncycastle.asn1.x509.Holder(new IssuerSerial(
62            new GeneralNames(new DERSequence(new GeneralName(issuerName))),
63            new DERInteger(serialNumber)));
64    }
65
66    public AttributeCertificateHolder(X500Principal issuerName,
67        BigInteger serialNumber)
68    {
69        this(X509Util.convertPrincipal(issuerName), serialNumber);
70    }
71
72    public AttributeCertificateHolder(X509Certificate cert)
73        throws CertificateParsingException
74    {
75        X509Principal name;
76
77        try
78        {
79            name = PrincipalUtil.getIssuerX509Principal(cert);
80        }
81        catch (Exception e)
82        {
83            throw new CertificateParsingException(e.getMessage());
84        }
85
86        holder = new Holder(new IssuerSerial(generateGeneralNames(name),
87            new DERInteger(cert.getSerialNumber())));
88    }
89
90    public AttributeCertificateHolder(X509Principal principal)
91    {
92        holder = new Holder(generateGeneralNames(principal));
93    }
94
95    public AttributeCertificateHolder(X500Principal principal)
96    {
97        this(X509Util.convertPrincipal(principal));
98    }
99
100    /**
101     * Constructs a holder for v2 attribute certificates with a hash value for
102     * some type of object.
103     * <p>
104     * <code>digestedObjectType</code> can be one of the following:
105     * <ul>
106     * <li>0 - publicKey - A hash of the public key of the holder must be
107     * passed.
108     * <li>1 - publicKeyCert - A hash of the public key certificate of the
109     * holder must be passed.
110     * <li>2 - otherObjectDigest - A hash of some other object type must be
111     * passed. <code>otherObjectTypeID</code> must not be empty.
112     * </ul>
113     * <p>
114     * This cannot be used if a v1 attribute certificate is used.
115     *
116     * @param digestedObjectType The digest object type.
117     * @param digestAlgorithm The algorithm identifier for the hash.
118     * @param otherObjectTypeID The object type ID if
119     *            <code>digestedObjectType</code> is
120     *            <code>otherObjectDigest</code>.
121     * @param objectDigest The hash value.
122     */
123    public AttributeCertificateHolder(int digestedObjectType,
124        String digestAlgorithm, String otherObjectTypeID, byte[] objectDigest)
125    {
126        holder = new Holder(new ObjectDigestInfo(digestedObjectType,
127            otherObjectTypeID, new AlgorithmIdentifier(digestAlgorithm), Arrays
128                .clone(objectDigest)));
129    }
130
131    /**
132     * Returns the digest object type if an object digest info is used.
133     * <p>
134     * <ul>
135     * <li>0 - publicKey - A hash of the public key of the holder must be
136     * passed.
137     * <li>1 - publicKeyCert - A hash of the public key certificate of the
138     * holder must be passed.
139     * <li>2 - otherObjectDigest - A hash of some other object type must be
140     * passed. <code>otherObjectTypeID</code> must not be empty.
141     * </ul>
142     *
143     * @return The digest object type or -1 if no object digest info is set.
144     */
145    public int getDigestedObjectType()
146    {
147        if (holder.getObjectDigestInfo() != null)
148        {
149            return holder.getObjectDigestInfo().getDigestedObjectType()
150                .getValue().intValue();
151        }
152        return -1;
153    }
154
155    /**
156     * Returns the other object type ID if an object digest info is used.
157     *
158     * @return The other object type ID or <code>null</code> if no object
159     *         digest info is set.
160     */
161    public String getDigestAlgorithm()
162    {
163        if (holder.getObjectDigestInfo() != null)
164        {
165            holder.getObjectDigestInfo().getDigestAlgorithm().getObjectId()
166                .getId();
167        }
168        return null;
169    }
170
171    /**
172     * Returns the hash if an object digest info is used.
173     *
174     * @return The hash or <code>null</code> if no object digest info is set.
175     */
176    public byte[] getObjectDigest()
177    {
178        if (holder.getObjectDigestInfo() != null)
179        {
180            holder.getObjectDigestInfo().getObjectDigest().getBytes();
181        }
182        return null;
183    }
184
185    /**
186     * Returns the digest algorithm ID if an object digest info is used.
187     *
188     * @return The digest algorithm ID or <code>null</code> if no object
189     *         digest info is set.
190     */
191    public String getOtherObjectTypeID()
192    {
193        if (holder.getObjectDigestInfo() != null)
194        {
195            holder.getObjectDigestInfo().getOtherObjectTypeID().getId();
196        }
197        return null;
198    }
199
200    private GeneralNames generateGeneralNames(X509Principal principal)
201    {
202        return new GeneralNames(new DERSequence(new GeneralName(principal)));
203    }
204
205    private boolean matchesDN(X509Principal subject, GeneralNames targets)
206    {
207        GeneralName[] names = targets.getNames();
208
209        for (int i = 0; i != names.length; i++)
210        {
211            GeneralName gn = names[i];
212
213            if (gn.getTagNo() == GeneralName.directoryName)
214            {
215                try
216                {
217                    if (new X509Principal(((ASN1Encodable)gn.getName())
218                        .getEncoded()).equals(subject))
219                    {
220                        return true;
221                    }
222                }
223                catch (IOException e)
224                {
225                }
226            }
227        }
228
229        return false;
230    }
231
232    private Object[] getNames(GeneralName[] names)
233    {
234        List l = new ArrayList(names.length);
235
236        for (int i = 0; i != names.length; i++)
237        {
238            if (names[i].getTagNo() == GeneralName.directoryName)
239            {
240                try
241                {
242                    l.add(new X500Principal(
243                        ((ASN1Encodable)names[i].getName()).getEncoded()));
244                }
245                catch (IOException e)
246                {
247                    throw new RuntimeException("badly formed Name object");
248                }
249            }
250        }
251
252        return l.toArray(new Object[l.size()]);
253    }
254
255    private Principal[] getPrincipals(GeneralNames names)
256    {
257        Object[] p = this.getNames(names.getNames());
258        List l = new ArrayList();
259
260        for (int i = 0; i != p.length; i++)
261        {
262            if (p[i] instanceof Principal)
263            {
264                l.add(p[i]);
265            }
266        }
267
268        return (Principal[])l.toArray(new Principal[l.size()]);
269    }
270
271    /**
272     * Return any principal objects inside the attribute certificate holder
273     * entity names field.
274     *
275     * @return an array of Principal objects (usually X500Principal), null if no
276     *         entity names field is set.
277     */
278    public Principal[] getEntityNames()
279    {
280        if (holder.getEntityName() != null)
281        {
282            return getPrincipals(holder.getEntityName());
283        }
284
285        return null;
286    }
287
288    /**
289     * Return the principals associated with the issuer attached to this holder
290     *
291     * @return an array of principals, null if no BaseCertificateID is set.
292     */
293    public Principal[] getIssuer()
294    {
295        if (holder.getBaseCertificateID() != null)
296        {
297            return getPrincipals(holder.getBaseCertificateID().getIssuer());
298        }
299
300        return null;
301    }
302
303    /**
304     * Return the serial number associated with the issuer attached to this
305     * holder.
306     *
307     * @return the certificate serial number, null if no BaseCertificateID is
308     *         set.
309     */
310    public BigInteger getSerialNumber()
311    {
312        if (holder.getBaseCertificateID() != null)
313        {
314            return holder.getBaseCertificateID().getSerial().getValue();
315        }
316
317        return null;
318    }
319
320    public Object clone()
321    {
322        return new AttributeCertificateHolder((ASN1Sequence)holder
323            .toASN1Object());
324    }
325
326    public boolean match(Certificate cert)
327    {
328        if (!(cert instanceof X509Certificate))
329        {
330            return false;
331        }
332
333        X509Certificate x509Cert = (X509Certificate)cert;
334
335        try
336        {
337            if (holder.getBaseCertificateID() != null)
338            {
339                return holder.getBaseCertificateID().getSerial().getValue().equals(x509Cert.getSerialNumber())
340                    && matchesDN(PrincipalUtil.getIssuerX509Principal(x509Cert), holder.getBaseCertificateID().getIssuer());
341            }
342
343            if (holder.getEntityName() != null)
344            {
345                if (matchesDN(PrincipalUtil.getSubjectX509Principal(x509Cert),
346                    holder.getEntityName()))
347                {
348                    return true;
349                }
350            }
351            if (holder.getObjectDigestInfo() != null)
352            {
353                MessageDigest md = null;
354                try
355                {
356                    md = MessageDigest.getInstance(getDigestAlgorithm(), "BC");
357
358                }
359                catch (Exception e)
360                {
361                    return false;
362                }
363                switch (getDigestedObjectType())
364                {
365                case ObjectDigestInfo.publicKey:
366                    // TODO: DSA Dss-parms
367                    md.update(cert.getPublicKey().getEncoded());
368                    break;
369                case ObjectDigestInfo.publicKeyCert:
370                    md.update(cert.getEncoded());
371                    break;
372                }
373                if (!Arrays.areEqual(md.digest(), getObjectDigest()))
374                {
375                    return false;
376                }
377            }
378        }
379        catch (CertificateEncodingException e)
380        {
381            return false;
382        }
383
384        return false;
385    }
386
387    public boolean equals(Object obj)
388    {
389        if (obj == this)
390        {
391            return true;
392        }
393
394        if (!(obj instanceof AttributeCertificateHolder))
395        {
396            return false;
397        }
398
399        AttributeCertificateHolder other = (AttributeCertificateHolder)obj;
400
401        return this.holder.equals(other.holder);
402    }
403
404    public int hashCode()
405    {
406        return this.holder.hashCode();
407    }
408
409    public boolean match(Object obj)
410    {
411        if (!(obj instanceof X509Certificate))
412        {
413            return false;
414        }
415
416        return match((Certificate)obj);
417    }
418}
419