aboutsummaryrefslogtreecommitdiff
path: root/src/share/classes/sun/security/provider/certpath/PolicyChecker.java
blob: dd030d3d9db78f706356d6204c44aa9793ffe043 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
/*
 * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.security.provider.certpath;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertPathValidatorException;
import java.security.cert.PKIXCertPathChecker;
import java.security.cert.PKIXReason;
import java.security.cert.PolicyNode;
import java.security.cert.PolicyQualifierInfo;
import java.security.cert.X509Certificate;
import java.util.*;

import sun.security.util.Debug;
import sun.security.x509.CertificatePoliciesExtension;
import sun.security.x509.PolicyConstraintsExtension;
import sun.security.x509.PolicyMappingsExtension;
import sun.security.x509.CertificatePolicyMap;
import static sun.security.x509.PKIXExtensions.*;
import sun.security.x509.PolicyInformation;
import sun.security.x509.X509CertImpl;
import sun.security.x509.InhibitAnyPolicyExtension;

/**
 * PolicyChecker is a <code>PKIXCertPathChecker</code> that checks policy
 * information on a PKIX certificate, namely certificate policies, policy
 * mappings, policy constraints and policy qualifiers.
 *
 * @since       1.4
 * @author      Yassir Elley
 */
class PolicyChecker extends PKIXCertPathChecker {

    private final Set<String> initPolicies;
    private final int certPathLen;
    private final boolean expPolicyRequired;
    private final boolean polMappingInhibited;
    private final boolean anyPolicyInhibited;
    private final boolean rejectPolicyQualifiers;
    private PolicyNodeImpl rootNode;
    private int explicitPolicy;
    private int policyMapping;
    private int inhibitAnyPolicy;
    private int certIndex;

    private Set<String> supportedExts;

    private static final Debug debug = Debug.getInstance("certpath");
    static final String ANY_POLICY = "2.5.29.32.0";

    /**
     * Constructs a Policy Checker.
     *
     * @param initialPolicies Set of initial policies
     * @param certPathLen length of the certification path to be checked
     * @param expPolicyRequired true if explicit policy is required
     * @param polMappingInhibited true if policy mapping is inhibited
     * @param anyPolicyInhibited true if the ANY_POLICY OID should be inhibited
     * @param rejectPolicyQualifiers true if pol qualifiers are to be rejected
     * @param rootNode the initial root node of the valid policy tree
     */
    PolicyChecker(Set<String> initialPolicies, int certPathLen,
        boolean expPolicyRequired, boolean polMappingInhibited,
        boolean anyPolicyInhibited, boolean rejectPolicyQualifiers,
        PolicyNodeImpl rootNode)
    {
        if (initialPolicies.isEmpty()) {
            // if no initialPolicies are specified by user, set
            // initPolicies to be anyPolicy by default
            this.initPolicies = new HashSet<String>(1);
            this.initPolicies.add(ANY_POLICY);
        } else {
            this.initPolicies = new HashSet<String>(initialPolicies);
        }
        this.certPathLen = certPathLen;
        this.expPolicyRequired = expPolicyRequired;
        this.polMappingInhibited = polMappingInhibited;
        this.anyPolicyInhibited = anyPolicyInhibited;
        this.rejectPolicyQualifiers = rejectPolicyQualifiers;
        this.rootNode = rootNode;
    }

    /**
     * Initializes the internal state of the checker from parameters
     * specified in the constructor
     *
     * @param forward a boolean indicating whether this checker should be
     *        initialized capable of building in the forward direction
     * @throws CertPathValidatorException if user wants to enable forward
     *         checking and forward checking is not supported.
     */
    @Override
    public void init(boolean forward) throws CertPathValidatorException {
        if (forward) {
            throw new CertPathValidatorException
                                        ("forward checking not supported");
        }

        certIndex = 1;
        explicitPolicy = (expPolicyRequired ? 0 : certPathLen + 1);
        policyMapping = (polMappingInhibited ? 0 : certPathLen + 1);
        inhibitAnyPolicy = (anyPolicyInhibited ? 0 : certPathLen + 1);
    }

    /**
     * Checks if forward checking is supported. Forward checking refers
     * to the ability of the PKIXCertPathChecker to perform its checks
     * when presented with certificates in the forward direction (from
     * target to anchor).
     *
     * @return true if forward checking is supported, false otherwise
     */
    @Override
    public boolean isForwardCheckingSupported() {
        return false;
    }

    /**
     * Gets an immutable Set of the OID strings for the extensions that
     * the PKIXCertPathChecker supports (i.e. recognizes, is able to
     * process), or null if no extensions are
     * supported. All OID strings that a PKIXCertPathChecker might
     * possibly be able to process should be included.
     *
     * @return the Set of extensions supported by this PKIXCertPathChecker,
     * or null if no extensions are supported
     */
    @Override
    public Set<String> getSupportedExtensions() {
        if (supportedExts == null) {
            supportedExts = new HashSet<String>(4);
            supportedExts.add(CertificatePolicies_Id.toString());
            supportedExts.add(PolicyMappings_Id.toString());
            supportedExts.add(PolicyConstraints_Id.toString());
            supportedExts.add(InhibitAnyPolicy_Id.toString());
            supportedExts = Collections.unmodifiableSet(supportedExts);
        }
        return supportedExts;
    }

    /**
     * Performs the policy processing checks on the certificate using its
     * internal state.
     *
     * @param cert the Certificate to be processed
     * @param unresCritExts the unresolved critical extensions
     * @throws CertPathValidatorException if the certificate does not verify
     */
    @Override
    public void check(Certificate cert, Collection<String> unresCritExts)
        throws CertPathValidatorException
    {
        // now do the policy checks
        checkPolicy((X509Certificate) cert);

        if (unresCritExts != null && !unresCritExts.isEmpty()) {
            unresCritExts.remove(CertificatePolicies_Id.toString());
            unresCritExts.remove(PolicyMappings_Id.toString());
            unresCritExts.remove(PolicyConstraints_Id.toString());
            unresCritExts.remove(InhibitAnyPolicy_Id.toString());
        }
    }

    /**
     * Internal method to run through all the checks.
     *
     * @param currCert the certificate to be processed
     * @exception CertPathValidatorException Exception thrown if
     * the certificate does not verify
     */
    private void checkPolicy(X509Certificate currCert)
        throws CertPathValidatorException
    {
        String msg = "certificate policies";
        if (debug != null) {
            debug.println("PolicyChecker.checkPolicy() ---checking " + msg
                + "...");
            debug.println("PolicyChecker.checkPolicy() certIndex = "
                + certIndex);
            debug.println("PolicyChecker.checkPolicy() BEFORE PROCESSING: "
                + "explicitPolicy = " + explicitPolicy);
            debug.println("PolicyChecker.checkPolicy() BEFORE PROCESSING: "
                + "policyMapping = " + policyMapping);
            debug.println("PolicyChecker.checkPolicy() BEFORE PROCESSING: "
                + "inhibitAnyPolicy = " + inhibitAnyPolicy);
            debug.println("PolicyChecker.checkPolicy() BEFORE PROCESSING: "
                + "policyTree = " + rootNode);
        }

        X509CertImpl currCertImpl = null;
        try {
            currCertImpl = X509CertImpl.toImpl(currCert);
        } catch (CertificateException ce) {
            throw new CertPathValidatorException(ce);
        }

        boolean finalCert = (certIndex == certPathLen);

        rootNode = processPolicies(certIndex, initPolicies, explicitPolicy,
            policyMapping, inhibitAnyPolicy, rejectPolicyQualifiers, rootNode,
            currCertImpl, finalCert);

        if (!finalCert) {
            explicitPolicy = mergeExplicitPolicy(explicitPolicy, currCertImpl,
                                                 finalCert);
            policyMapping = mergePolicyMapping(policyMapping, currCertImpl);
            inhibitAnyPolicy = mergeInhibitAnyPolicy(inhibitAnyPolicy,
                                                     currCertImpl);
        }

        certIndex++;

        if (debug != null) {
            debug.println("PolicyChecker.checkPolicy() AFTER PROCESSING: "
                + "explicitPolicy = " + explicitPolicy);
            debug.println("PolicyChecker.checkPolicy() AFTER PROCESSING: "
                + "policyMapping = " + policyMapping);
            debug.println("PolicyChecker.checkPolicy() AFTER PROCESSING: "
                + "inhibitAnyPolicy = " + inhibitAnyPolicy);
            debug.println("PolicyChecker.checkPolicy() AFTER PROCESSING: "
                + "policyTree = " + rootNode);
            debug.println("PolicyChecker.checkPolicy() " + msg + " verified");
        }
    }

    /**
     * Merges the specified explicitPolicy value with the
     * requireExplicitPolicy field of the <code>PolicyConstraints</code>
     * extension obtained from the certificate. An explicitPolicy
     * value of -1 implies no constraint.
     *
     * @param explicitPolicy an integer which indicates if a non-null
     * valid policy tree is required
     * @param currCert the Certificate to be processed
     * @param finalCert a boolean indicating whether currCert is
     * the final cert in the cert path
     * @return returns the new explicitPolicy value
     * @exception CertPathValidatorException Exception thrown if an error
     * occurs
     */
    static int mergeExplicitPolicy(int explicitPolicy, X509CertImpl currCert,
        boolean finalCert) throws CertPathValidatorException
    {
        if ((explicitPolicy > 0) && !X509CertImpl.isSelfIssued(currCert)) {
            explicitPolicy--;
        }

        try {
            PolicyConstraintsExtension polConstExt
                = currCert.getPolicyConstraintsExtension();
            if (polConstExt == null)
                return explicitPolicy;
            int require =
                polConstExt.get(PolicyConstraintsExtension.REQUIRE).intValue();
            if (debug != null) {
                debug.println("PolicyChecker.mergeExplicitPolicy() "
                   + "require Index from cert = " + require);
            }
            if (!finalCert) {
                if (require != -1) {
                    if ((explicitPolicy == -1) || (require < explicitPolicy)) {
                        explicitPolicy = require;
                    }
                }
            } else {
                if (require == 0)
                    explicitPolicy = require;
            }
        } catch (IOException e) {
            if (debug != null) {
                debug.println("PolicyChecker.mergeExplicitPolicy "
                              + "unexpected exception");
                e.printStackTrace();
            }
            throw new CertPathValidatorException(e);
        }

        return explicitPolicy;
    }

    /**
     * Merges the specified policyMapping value with the
     * inhibitPolicyMapping field of the <code>PolicyConstraints</code>
     * extension obtained from the certificate. A policyMapping
     * value of -1 implies no constraint.
     *
     * @param policyMapping an integer which indicates if policy mapping
     * is inhibited
     * @param currCert the Certificate to be processed
     * @return returns the new policyMapping value
     * @exception CertPathValidatorException Exception thrown if an error
     * occurs
     */
    static int mergePolicyMapping(int policyMapping, X509CertImpl currCert)
        throws CertPathValidatorException
    {
        if ((policyMapping > 0) && !X509CertImpl.isSelfIssued(currCert)) {
            policyMapping--;
        }

        try {
            PolicyConstraintsExtension polConstExt
                = currCert.getPolicyConstraintsExtension();
            if (polConstExt == null)
                return policyMapping;

            int inhibit =
                polConstExt.get(PolicyConstraintsExtension.INHIBIT).intValue();
            if (debug != null)
                debug.println("PolicyChecker.mergePolicyMapping() "
                    + "inhibit Index from cert = " + inhibit);

            if (inhibit != -1) {
                if ((policyMapping == -1) || (inhibit < policyMapping)) {
                    policyMapping = inhibit;
                }
            }
        } catch (IOException e) {
            if (debug != null) {
                debug.println("PolicyChecker.mergePolicyMapping "
                              + "unexpected exception");
                e.printStackTrace();
            }
            throw new CertPathValidatorException(e);
        }

        return policyMapping;
    }

    /**
     * Merges the specified inhibitAnyPolicy value with the
     * SkipCerts value of the InhibitAnyPolicy
     * extension obtained from the certificate.
     *
     * @param inhibitAnyPolicy an integer which indicates whether
     * "any-policy" is considered a match
     * @param currCert the Certificate to be processed
     * @return returns the new inhibitAnyPolicy value
     * @exception CertPathValidatorException Exception thrown if an error
     * occurs
     */
    static int mergeInhibitAnyPolicy(int inhibitAnyPolicy,
        X509CertImpl currCert) throws CertPathValidatorException
    {
        if ((inhibitAnyPolicy > 0) && !X509CertImpl.isSelfIssued(currCert)) {
            inhibitAnyPolicy--;
        }

        try {
            InhibitAnyPolicyExtension inhAnyPolExt = (InhibitAnyPolicyExtension)
                currCert.getExtension(InhibitAnyPolicy_Id);
            if (inhAnyPolExt == null)
                return inhibitAnyPolicy;

            int skipCerts =
                inhAnyPolExt.get(InhibitAnyPolicyExtension.SKIP_CERTS).intValue();
            if (debug != null)
                debug.println("PolicyChecker.mergeInhibitAnyPolicy() "
                    + "skipCerts Index from cert = " + skipCerts);

            if (skipCerts != -1) {
                if (skipCerts < inhibitAnyPolicy) {
                    inhibitAnyPolicy = skipCerts;
                }
            }
        } catch (IOException e) {
            if (debug != null) {
                debug.println("PolicyChecker.mergeInhibitAnyPolicy "
                              + "unexpected exception");
                e.printStackTrace();
            }
            throw new CertPathValidatorException(e);
        }

        return inhibitAnyPolicy;
    }

    /**
     * Processes certificate policies in the certificate.
     *
     * @param certIndex the index of the certificate
     * @param initPolicies the initial policies required by the user
     * @param explicitPolicy an integer which indicates if a non-null
     * valid policy tree is required
     * @param policyMapping an integer which indicates if policy
     * mapping is inhibited
     * @param inhibitAnyPolicy an integer which indicates whether
     * "any-policy" is considered a match
     * @param rejectPolicyQualifiers a boolean indicating whether the
     * user wants to reject policies that have qualifiers
     * @param origRootNode the root node of the valid policy tree
     * @param currCert the Certificate to be processed
     * @param finalCert a boolean indicating whether currCert is the final
     * cert in the cert path
     * @return the root node of the valid policy tree after modification
     * @exception CertPathValidatorException Exception thrown if an
     * error occurs while processing policies.
     */
    static PolicyNodeImpl processPolicies(int certIndex, Set<String> initPolicies,
        int explicitPolicy, int policyMapping, int inhibitAnyPolicy,
        boolean rejectPolicyQualifiers, PolicyNodeImpl origRootNode,
        X509CertImpl currCert, boolean finalCert)
        throws CertPathValidatorException
    {
        boolean policiesCritical = false;
        List<PolicyInformation> policyInfo;
        PolicyNodeImpl rootNode = null;
        Set<PolicyQualifierInfo> anyQuals = new HashSet<>();

        if (origRootNode == null)
            rootNode = null;
        else
            rootNode = origRootNode.copyTree();

        // retrieve policyOIDs from currCert
        CertificatePoliciesExtension currCertPolicies
            = currCert.getCertificatePoliciesExtension();

        // PKIX: Section 6.1.3: Step (d)
        if ((currCertPolicies != null) && (rootNode != null)) {
            policiesCritical = currCertPolicies.isCritical();
            if (debug != null)
                debug.println("PolicyChecker.processPolicies() "
                    + "policiesCritical = " + policiesCritical);

            try {
                policyInfo = currCertPolicies.get(CertificatePoliciesExtension.POLICIES);
            } catch (IOException ioe) {
                throw new CertPathValidatorException("Exception while "
                    + "retrieving policyOIDs", ioe);
            }

            if (debug != null)
                debug.println("PolicyChecker.processPolicies() "
                    + "rejectPolicyQualifiers = " + rejectPolicyQualifiers);

            boolean foundAnyPolicy = false;

            // process each policy in cert
            for (PolicyInformation curPolInfo : policyInfo) {
                String curPolicy =
                    curPolInfo.getPolicyIdentifier().getIdentifier().toString();

                if (curPolicy.equals(ANY_POLICY)) {
                    foundAnyPolicy = true;
                    anyQuals = curPolInfo.getPolicyQualifiers();
                } else {
                    // PKIX: Section 6.1.3: Step (d)(1)
                    if (debug != null)
                        debug.println("PolicyChecker.processPolicies() "
                                      + "processing policy: " + curPolicy);

                    // retrieve policy qualifiers from cert
                    Set<PolicyQualifierInfo> pQuals =
                                        curPolInfo.getPolicyQualifiers();

                    // reject cert if we find critical policy qualifiers and
                    // the policyQualifiersRejected flag is set in the params
                    if (!pQuals.isEmpty() && rejectPolicyQualifiers &&
                        policiesCritical) {
                        throw new CertPathValidatorException(
                            "critical policy qualifiers present in certificate",
                            null, null, -1, PKIXReason.INVALID_POLICY);
                    }

                    // PKIX: Section 6.1.3: Step (d)(1)(i)
                    boolean foundMatch = processParents(certIndex,
                        policiesCritical, rejectPolicyQualifiers, rootNode,
                        curPolicy, pQuals, false);

                    if (!foundMatch) {
                        // PKIX: Section 6.1.3: Step (d)(1)(ii)
                        processParents(certIndex, policiesCritical,
                            rejectPolicyQualifiers, rootNode, curPolicy,
                            pQuals, true);
                    }
                }
            }

            // PKIX: Section 6.1.3: Step (d)(2)
            if (foundAnyPolicy) {
                if ((inhibitAnyPolicy > 0) ||
                        (!finalCert && X509CertImpl.isSelfIssued(currCert))) {
                    if (debug != null) {
                        debug.println("PolicyChecker.processPolicies() "
                            + "processing policy: " + ANY_POLICY);
                    }
                    processParents(certIndex, policiesCritical,
                        rejectPolicyQualifiers, rootNode, ANY_POLICY, anyQuals,
                        true);
                }
            }

            // PKIX: Section 6.1.3: Step (d)(3)
            rootNode.prune(certIndex);
            if (!rootNode.getChildren().hasNext()) {
                rootNode = null;
            }
        } else if (currCertPolicies == null) {
            if (debug != null)
                debug.println("PolicyChecker.processPolicies() "
                              + "no policies present in cert");
            // PKIX: Section 6.1.3: Step (e)
            rootNode = null;
        }

        // We delay PKIX: Section 6.1.3: Step (f) to the end
        // because the code that follows may delete some nodes
        // resulting in a null tree
        if (rootNode != null) {
            if (!finalCert) {
                // PKIX: Section 6.1.4: Steps (a)-(b)
                rootNode = processPolicyMappings(currCert, certIndex,
                    policyMapping, rootNode, policiesCritical, anyQuals);
            }
        }

        // At this point, we optimize the PKIX algorithm by
        // removing those nodes which would later have
        // been removed by PKIX: Section 6.1.5: Step (g)(iii)

        if ((rootNode != null) && (!initPolicies.contains(ANY_POLICY))
            && (currCertPolicies != null)) {
            rootNode = removeInvalidNodes(rootNode, certIndex,
                                          initPolicies, currCertPolicies);

            // PKIX: Section 6.1.5: Step (g)(iii)
            if ((rootNode != null) && finalCert) {
                // rewrite anyPolicy leaf nodes (see method comments)
                rootNode = rewriteLeafNodes(certIndex, initPolicies, rootNode);
            }
        }


        if (finalCert) {
            // PKIX: Section 6.1.5: Steps (a) and (b)
            explicitPolicy = mergeExplicitPolicy(explicitPolicy, currCert,
                                             finalCert);
        }

        // PKIX: Section 6.1.3: Step (f)
        // verify that either explicit policy is greater than 0 or
        // the valid_policy_tree is not equal to NULL

        if ((explicitPolicy == 0) && (rootNode == null)) {
            throw new CertPathValidatorException
                ("non-null policy tree required and policy tree is null",
                 null, null, -1, PKIXReason.INVALID_POLICY);
        }

        return rootNode;
    }

    /**
     * Rewrite leaf nodes at the end of validation as described in RFC 5280
     * section 6.1.5: Step (g)(iii). Leaf nodes with anyPolicy are replaced
     * by nodes explicitly representing initial policies not already
     * represented by leaf nodes.
     *
     * This method should only be called when processing the final cert
     * and if the policy tree is not null and initial policies is not
     * anyPolicy.
     *
     * @param certIndex the depth of the tree
     * @param initPolicies Set of user specified initial policies
     * @param rootNode the root of the policy tree
     */
    private static PolicyNodeImpl rewriteLeafNodes(int certIndex,
            Set<String> initPolicies, PolicyNodeImpl rootNode) {
        Set<PolicyNodeImpl> anyNodes =
                        rootNode.getPolicyNodesValid(certIndex, ANY_POLICY);
        if (anyNodes.isEmpty()) {
            return rootNode;
        }
        PolicyNodeImpl anyNode = anyNodes.iterator().next();
        PolicyNodeImpl parentNode = (PolicyNodeImpl)anyNode.getParent();
        parentNode.deleteChild(anyNode);
        // see if there are any initialPolicies not represented by leaf nodes
        Set<String> initial = new HashSet<>(initPolicies);
        for (PolicyNodeImpl node : rootNode.getPolicyNodes(certIndex)) {
            initial.remove(node.getValidPolicy());
        }
        if (initial.isEmpty()) {
            // we deleted the anyPolicy node and have nothing to re-add,
            // so we need to prune the tree
            rootNode.prune(certIndex);
            if (rootNode.getChildren().hasNext() == false) {
                rootNode = null;
            }
        } else {
            boolean anyCritical = anyNode.isCritical();
            Set<PolicyQualifierInfo> anyQualifiers =
                                                anyNode.getPolicyQualifiers();
            for (String policy : initial) {
                Set<String> expectedPolicies = Collections.singleton(policy);
                PolicyNodeImpl node = new PolicyNodeImpl(parentNode, policy,
                    anyQualifiers, anyCritical, expectedPolicies, false);
            }
        }
        return rootNode;
    }

    /**
     * Finds the policy nodes of depth (certIndex-1) where curPolicy
     * is in the expected policy set and creates a new child node
     * appropriately. If matchAny is true, then a value of ANY_POLICY
     * in the expected policy set will match any curPolicy. If matchAny
     * is false, then the expected policy set must exactly contain the
     * curPolicy to be considered a match. This method returns a boolean
     * value indicating whether a match was found.
     *
     * @param certIndex the index of the certificate whose policy is
     * being processed
     * @param policiesCritical a boolean indicating whether the certificate
     * policies extension is critical
     * @param rejectPolicyQualifiers a boolean indicating whether the
     * user wants to reject policies that have qualifiers
     * @param rootNode the root node of the valid policy tree
     * @param curPolicy a String representing the policy being processed
     * @param pQuals the policy qualifiers of the policy being processed or an
     * empty Set if there are no qualifiers
     * @param matchAny a boolean indicating whether a value of ANY_POLICY
     * in the expected policy set will be considered a match
     * @return a boolean indicating whether a match was found
     * @exception CertPathValidatorException Exception thrown if error occurs.
     */
    private static boolean processParents(int certIndex,
        boolean policiesCritical, boolean rejectPolicyQualifiers,
        PolicyNodeImpl rootNode, String curPolicy,
        Set<PolicyQualifierInfo> pQuals,
        boolean matchAny) throws CertPathValidatorException
    {
        boolean foundMatch = false;

        if (debug != null)
            debug.println("PolicyChecker.processParents(): matchAny = "
                + matchAny);

        // find matching parents
        Set<PolicyNodeImpl> parentNodes =
                rootNode.getPolicyNodesExpected(certIndex - 1,
                                                curPolicy, matchAny);

        // for each matching parent, extend policy tree
        for (PolicyNodeImpl curParent : parentNodes) {
            if (debug != null)
                debug.println("PolicyChecker.processParents() "
                              + "found parent:\n" + curParent.asString());

            foundMatch = true;
            String curParPolicy = curParent.getValidPolicy();

            PolicyNodeImpl curNode = null;
            Set<String> curExpPols = null;

            if (curPolicy.equals(ANY_POLICY)) {
                // do step 2
                Set<String> parExpPols = curParent.getExpectedPolicies();
            parentExplicitPolicies:
                for (String curParExpPol : parExpPols) {

                    Iterator<PolicyNodeImpl> childIter =
                                        curParent.getChildren();
                    while (childIter.hasNext()) {
                        PolicyNodeImpl childNode = childIter.next();
                        String childPolicy = childNode.getValidPolicy();
                        if (curParExpPol.equals(childPolicy)) {
                            if (debug != null)
                                debug.println(childPolicy + " in parent's "
                                    + "expected policy set already appears in "
                                    + "child node");
                            continue parentExplicitPolicies;
                        }
                    }

                    Set<String> expPols = new HashSet<>();
                    expPols.add(curParExpPol);

                    curNode = new PolicyNodeImpl
                        (curParent, curParExpPol, pQuals,
                         policiesCritical, expPols, false);
                }
            } else {
                curExpPols = new HashSet<String>();
                curExpPols.add(curPolicy);

                curNode = new PolicyNodeImpl
                    (curParent, curPolicy, pQuals,
                     policiesCritical, curExpPols, false);
            }
        }

        return foundMatch;
    }

    /**
     * Processes policy mappings in the certificate.
     *
     * @param currCert the Certificate to be processed
     * @param certIndex the index of the current certificate
     * @param policyMapping an integer which indicates if policy
     * mapping is inhibited
     * @param rootNode the root node of the valid policy tree
     * @param policiesCritical a boolean indicating if the certificate policies
     * extension is critical
     * @param anyQuals the qualifiers associated with ANY-POLICY, or an empty
     * Set if there are no qualifiers associated with ANY-POLICY
     * @return the root node of the valid policy tree after modification
     * @exception CertPathValidatorException exception thrown if an error
     * occurs while processing policy mappings
     */
    private static PolicyNodeImpl processPolicyMappings(X509CertImpl currCert,
        int certIndex, int policyMapping, PolicyNodeImpl rootNode,
        boolean policiesCritical, Set<PolicyQualifierInfo> anyQuals)
        throws CertPathValidatorException
    {
        PolicyMappingsExtension polMappingsExt
            = currCert.getPolicyMappingsExtension();

        if (polMappingsExt == null)
            return rootNode;

        if (debug != null)
            debug.println("PolicyChecker.processPolicyMappings() "
                + "inside policyMapping check");

        List<CertificatePolicyMap> maps = null;
        try {
            maps = polMappingsExt.get(PolicyMappingsExtension.MAP);
        } catch (IOException e) {
            if (debug != null) {
                debug.println("PolicyChecker.processPolicyMappings() "
                    + "mapping exception");
                e.printStackTrace();
            }
            throw new CertPathValidatorException("Exception while checking "
                                                 + "mapping", e);
        }

        boolean childDeleted = false;
        for (CertificatePolicyMap polMap : maps) {
            String issuerDomain
                = polMap.getIssuerIdentifier().getIdentifier().toString();
            String subjectDomain
                = polMap.getSubjectIdentifier().getIdentifier().toString();
            if (debug != null) {
                debug.println("PolicyChecker.processPolicyMappings() "
                              + "issuerDomain = " + issuerDomain);
                debug.println("PolicyChecker.processPolicyMappings() "
                              + "subjectDomain = " + subjectDomain);
            }

            if (issuerDomain.equals(ANY_POLICY)) {
                throw new CertPathValidatorException
                    ("encountered an issuerDomainPolicy of ANY_POLICY",
                     null, null, -1, PKIXReason.INVALID_POLICY);
            }

            if (subjectDomain.equals(ANY_POLICY)) {
                throw new CertPathValidatorException
                    ("encountered a subjectDomainPolicy of ANY_POLICY",
                     null, null, -1, PKIXReason.INVALID_POLICY);
            }

            Set<PolicyNodeImpl> validNodes =
                rootNode.getPolicyNodesValid(certIndex, issuerDomain);
            if (!validNodes.isEmpty()) {
                for (PolicyNodeImpl curNode : validNodes) {
                    if ((policyMapping > 0) || (policyMapping == -1)) {
                        curNode.addExpectedPolicy(subjectDomain);
                    } else if (policyMapping == 0) {
                        PolicyNodeImpl parentNode =
                            (PolicyNodeImpl) curNode.getParent();
                        if (debug != null)
                            debug.println("PolicyChecker.processPolicyMappings"
                                + "() before deleting: policy tree = "
                                + rootNode);
                        parentNode.deleteChild(curNode);
                        childDeleted = true;
                        if (debug != null)
                            debug.println("PolicyChecker.processPolicyMappings"
                                + "() after deleting: policy tree = "
                                + rootNode);
                    }
                }
            } else { // no node of depth i has a valid policy
                if ((policyMapping > 0) || (policyMapping == -1)) {
                    Set<PolicyNodeImpl> validAnyNodes =
                        rootNode.getPolicyNodesValid(certIndex, ANY_POLICY);
                    for (PolicyNodeImpl curAnyNode : validAnyNodes) {
                        PolicyNodeImpl curAnyNodeParent =
                            (PolicyNodeImpl) curAnyNode.getParent();

                        Set<String> expPols = new HashSet<>();
                        expPols.add(subjectDomain);

                        PolicyNodeImpl curNode = new PolicyNodeImpl
                            (curAnyNodeParent, issuerDomain, anyQuals,
                             policiesCritical, expPols, true);
                    }
                }
            }
        }

        if (childDeleted) {
            rootNode.prune(certIndex);
            if (!rootNode.getChildren().hasNext()) {
                if (debug != null)
                    debug.println("setting rootNode to null");
                rootNode = null;
            }
        }

        return rootNode;
    }

    /**
     * Removes those nodes which do not intersect with the initial policies
     * specified by the user.
     *
     * @param rootNode the root node of the valid policy tree
     * @param certIndex the index of the certificate being processed
     * @param initPolicies the Set of policies required by the user
     * @param currCertPolicies the CertificatePoliciesExtension of the
     * certificate being processed
     * @returns the root node of the valid policy tree after modification
     * @exception CertPathValidatorException Exception thrown if error occurs.
     */
    private static PolicyNodeImpl removeInvalidNodes(PolicyNodeImpl rootNode,
        int certIndex, Set<String> initPolicies,
        CertificatePoliciesExtension currCertPolicies)
        throws CertPathValidatorException
    {
        List<PolicyInformation> policyInfo = null;
        try {
            policyInfo = currCertPolicies.get(CertificatePoliciesExtension.POLICIES);
        } catch (IOException ioe) {
            throw new CertPathValidatorException("Exception while "
                + "retrieving policyOIDs", ioe);
        }

        boolean childDeleted = false;
        for (PolicyInformation curPolInfo : policyInfo) {
            String curPolicy =
                curPolInfo.getPolicyIdentifier().getIdentifier().toString();

            if (debug != null)
                debug.println("PolicyChecker.processPolicies() "
                              + "processing policy second time: " + curPolicy);

            Set<PolicyNodeImpl> validNodes =
                        rootNode.getPolicyNodesValid(certIndex, curPolicy);
            for (PolicyNodeImpl curNode : validNodes) {
                PolicyNodeImpl parentNode = (PolicyNodeImpl)curNode.getParent();
                if (parentNode.getValidPolicy().equals(ANY_POLICY)) {
                    if ((!initPolicies.contains(curPolicy)) &&
                        (!curPolicy.equals(ANY_POLICY))) {
                        if (debug != null)
                            debug.println("PolicyChecker.processPolicies() "
                                + "before deleting: policy tree = " + rootNode);
                        parentNode.deleteChild(curNode);
                        childDeleted = true;
                        if (debug != null)
                            debug.println("PolicyChecker.processPolicies() "
                                + "after deleting: policy tree = " + rootNode);
                    }
                }
            }
        }

        if (childDeleted) {
            rootNode.prune(certIndex);
            if (!rootNode.getChildren().hasNext()) {
                rootNode = null;
            }
        }

        return rootNode;
    }

    /**
     * Gets the root node of the valid policy tree, or null if the
     * valid policy tree is null. Marks each node of the returned tree
     * immutable and thread-safe.
     *
     * @returns the root node of the valid policy tree, or null if
     * the valid policy tree is null
     */
    PolicyNode getPolicyTree() {
        if (rootNode == null)
            return null;
        else {
            PolicyNodeImpl policyTree = rootNode.copyTree();
            policyTree.setImmutable();
            return policyTree;
        }
    }
}