1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18/**
19* @author Vladimir N. Molotkov, Alexander Y. Kleymenov
20* @version $Revision$
21*/
22
23package org.apache.harmony.security.x509;
24
25import java.io.IOException;
26import java.security.cert.X509Certificate;
27import java.util.ArrayList;
28import java.util.List;
29import org.apache.harmony.security.asn1.ASN1Implicit;
30import org.apache.harmony.security.asn1.ASN1OctetString;
31import org.apache.harmony.security.asn1.ASN1Sequence;
32import org.apache.harmony.security.asn1.ASN1Type;
33import org.apache.harmony.security.asn1.BerInputStream;
34
35/**
36 * The class encapsulates the ASN.1 DER encoding/decoding work
37 * with the following structure which is a part of X.509 certificate
38 * (as specified in RFC 3280 -
39 *  Internet X.509 Public Key Infrastructure.
40 *  Certificate and Certificate Revocation List (CRL) Profile.
41 *  http://www.ietf.org/rfc/rfc3280.txt):
42 *
43 * <pre>
44 *
45 *   NameConstraints ::= SEQUENCE {
46 *        permittedSubtrees       [0]     GeneralSubtrees OPTIONAL,
47 *        excludedSubtrees        [1]     GeneralSubtrees OPTIONAL }
48 *
49 *   GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
50 *
51 * </pre>
52 *
53 * @see org.apache.harmony.security.x509.GeneralSubtree
54 * @see org.apache.harmony.security.x509.GeneralName
55 */
56public final class NameConstraints extends ExtensionValue {
57    /** the value of permittedSubtrees field of the structure */
58    private final GeneralSubtrees permittedSubtrees;
59    /** the value of excludedSubtrees field of the structure */
60    private final GeneralSubtrees excludedSubtrees;
61    /** the ASN.1 encoded form of NameConstraints */
62    private byte[] encoding;
63
64    private ArrayList<GeneralName>[] permitted_names;
65    private ArrayList<GeneralName>[] excluded_names;
66
67    /**
68     * Constructs <code>NameConstrains</code> object
69     */
70    public NameConstraints(GeneralSubtrees permittedSubtrees,
71                           GeneralSubtrees excludedSubtrees) {
72        if (permittedSubtrees != null) {
73            List<GeneralSubtree> ps = permittedSubtrees.getSubtrees();
74            if (ps == null || ps.isEmpty()) {
75                throw new IllegalArgumentException("permittedSubtrees are empty");
76            }
77        }
78        if (excludedSubtrees != null) {
79            List<GeneralSubtree> es = excludedSubtrees.getSubtrees();
80            if (es == null || es.isEmpty()) {
81                throw new IllegalArgumentException("excludedSubtrees are empty");
82            }
83        }
84        this.permittedSubtrees = permittedSubtrees;
85        this.excludedSubtrees = excludedSubtrees;
86    }
87
88    private NameConstraints(GeneralSubtrees permittedSubtrees,
89                            GeneralSubtrees excludedSubtrees, byte[] encoding) {
90        this(permittedSubtrees, excludedSubtrees);
91        this.encoding = encoding;
92    }
93
94    public static NameConstraints decode(byte[] encoding) throws IOException {
95        return (NameConstraints) ASN1.decode(encoding);
96    }
97
98    @Override public byte[] getEncoded() {
99        if (encoding == null) {
100            encoding = ASN1.encode(this);
101        }
102        return encoding;
103    }
104
105    /**
106     * Prepare the data structure to speed up the checking process.
107     */
108    private void prepareNames() {
109        // array of lists with permitted General Names divided by type
110        permitted_names = new ArrayList[9];
111        if (permittedSubtrees != null) {
112            for (GeneralSubtree generalSubtree : permittedSubtrees.getSubtrees()) {
113                GeneralName name = generalSubtree.getBase();
114                int tag = name.getTag();
115                if (permitted_names[tag] == null) {
116                    permitted_names[tag] = new ArrayList<GeneralName>();
117                }
118                permitted_names[tag].add(name);
119            }
120        }
121        // array of lists with excluded General Names divided by type
122        excluded_names = new ArrayList[9];
123        if (excludedSubtrees != null) {
124            for (GeneralSubtree generalSubtree : excludedSubtrees.getSubtrees()) {
125                GeneralName name = generalSubtree.getBase();
126                int tag = name.getTag();
127                if (excluded_names[tag] == null) {
128                    excluded_names[tag] = new ArrayList<GeneralName>();
129                }
130                excluded_names[tag].add(name);
131            }
132        }
133    }
134
135    /**
136     * Returns the value of certificate extension
137     */
138    private byte[] getExtensionValue(X509Certificate cert, String OID) {
139        try {
140            byte[] bytes = cert.getExtensionValue(OID);
141            if (bytes == null) {
142                return null;
143            }
144            return (byte[]) ASN1OctetString.getInstance().decode(bytes);
145        } catch (IOException e) {
146            return null;
147        }
148    }
149
150    /**
151     * Apply the name restrictions specified by this NameConstraints
152     * instance to the subject distinguished name and subject alternative
153     * names of specified X509Certificate. Restrictions apply only
154     * if specified name form is present in the certificate.
155     * The restrictions are applied according the RFC 3280
156     * (see 4.2.1.11 Name Constraints), excepting that restrictions are applied
157     * and to CA certificates, and to certificates which issuer and subject
158     * names the same (i.e. method does not check if it CA's certificate or not,
159     * or if the names differ or not. This check if it is needed should be done
160     * by caller before calling this method).
161     * @param   cert X.509 Certificate to be checked.
162     * @return  true if the certificate is acceptable according
163     *          these NameConstraints restrictions
164     */
165    public boolean isAcceptable(X509Certificate cert) {
166        if (permitted_names == null) {
167            prepareNames();
168        }
169
170        byte[] bytes = getExtensionValue(cert, "2.5.29.17");
171        List<GeneralName> names;
172        try {
173            names = (bytes == null)
174                ? new ArrayList<GeneralName>(1) // will check the subject field only
175                : ((GeneralNames) GeneralNames.ASN1.decode(bytes)).getNames();
176        } catch (IOException e) {
177            // the certificate is broken;
178            e.printStackTrace();
179            return false;
180        }
181        if ((excluded_names[4] != null) || (permitted_names[4] != null)) {
182            try {
183                names.add(new GeneralName(4,
184                        cert.getSubjectX500Principal().getName()));
185            } catch (IOException e) {
186                // should never be happened
187            }
188        }
189        return isAcceptable(names);
190    }
191
192    /**
193     * Check if this list of names is acceptable according to this
194     * NameConstraints object.
195     */
196    public boolean isAcceptable(List<GeneralName> names) {
197        if (permitted_names == null) {
198            prepareNames();
199        }
200
201        // check map: shows which types of permitted alternative names are
202        // presented in the certificate
203        boolean[] types_presented = new boolean[9];
204        // check map: shows if permitted name of presented type is found
205        // among the certificate's alternative names
206        boolean[] permitted_found = new boolean[9];
207        for (GeneralName name : names) {
208            int type = name.getTag();
209            // search the name in excluded names
210            if (excluded_names[type] != null) {
211                for (int i = 0; i < excluded_names[type].size(); i++) {
212                    if (excluded_names[type].get(i).isAcceptable(name)) {
213                        return false;
214                    }
215                }
216            }
217            // Search the name in permitted names
218            // (if we already found the name of such type between the alt
219            // names - we do not need to check others)
220            if ((permitted_names[type] != null) && (!permitted_found[type])) {
221                types_presented[type] = true;
222                for (int i = 0; i < permitted_names[type].size(); i++) {
223                    if (permitted_names[type].get(i).isAcceptable(name)) {
224                        // found one permitted name of such type
225                        permitted_found[type] = true;
226                    }
227                }
228            }
229        }
230        for (int type = 0; type < 9; type++) {
231            if (types_presented[type] && !permitted_found[type]) {
232                return false;
233            }
234        }
235        return true;
236    }
237
238    @Override public void dumpValue(StringBuilder sb, String prefix) {
239        sb.append(prefix).append("Name Constraints: [\n");
240        if (permittedSubtrees != null) {
241            sb.append(prefix).append("  Permitted: [\n");
242            for (GeneralSubtree generalSubtree : permittedSubtrees.getSubtrees()) {
243                generalSubtree.dumpValue(sb, prefix + "    ");
244            }
245            sb.append(prefix).append("  ]\n");
246        }
247        if (excludedSubtrees != null) {
248            sb.append(prefix).append("  Excluded: [\n");
249            for (GeneralSubtree generalSubtree : excludedSubtrees.getSubtrees()) {
250                generalSubtree.dumpValue(sb, prefix + "    ");
251            }
252            sb.append(prefix).append("  ]\n");
253        }
254        sb.append('\n').append(prefix).append("]\n");
255    }
256
257    /**
258     * X.509 NameConstraints encoder/decoder.
259     */
260    public static final ASN1Sequence ASN1 = new ASN1Sequence(new ASN1Type[] {
261            new ASN1Implicit(0, GeneralSubtrees.ASN1),
262            new ASN1Implicit(1, GeneralSubtrees.ASN1) }) {
263        {
264            setOptional(0);
265            setOptional(1);
266        }
267
268        @Override protected Object getDecodedObject(BerInputStream in) {
269            Object[] values = (Object[]) in.content;
270            return new NameConstraints(
271                    (GeneralSubtrees) values[0],
272                    (GeneralSubtrees) values[1],
273                    in.getEncoded());
274        }
275
276        @Override protected void getValues(Object object, Object[] values) {
277            NameConstraints nc = (NameConstraints) object;
278            values[0] = nc.permittedSubtrees;
279            values[1] = nc.excludedSubtrees;
280        }
281    };
282}
283