• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ebourg / jsign / #397

27 Mar 2026 08:06AM UTC coverage: 80.72% (+0.04%) from 80.685%
#397

push

ebourg
Fixed the documentation of the --verbose and --debug options

4978 of 6167 relevant lines covered (80.72%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.55
/jsign-crypto/src/main/java/net/jsign/CertificateUtils.java
1
/*
2
 * Copyright 2023 Emmanuel Bourg
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package net.jsign;
18

19
import java.io.File;
20
import java.io.FileInputStream;
21
import java.io.IOException;
22
import java.io.InputStream;
23
import java.net.URI;
24
import java.net.URL;
25
import java.security.cert.Certificate;
26
import java.security.cert.CertificateException;
27
import java.security.cert.CertificateFactory;
28
import java.security.cert.X509Certificate;
29
import java.util.ArrayList;
30
import java.util.Collection;
31
import java.util.Collections;
32
import java.util.Comparator;
33
import java.util.HashSet;
34
import java.util.LinkedHashSet;
35
import java.util.List;
36
import java.util.Set;
37
import java.util.stream.Collectors;
38
import javax.security.auth.x500.X500Principal;
39

40
import org.bouncycastle.asn1.ASN1OctetString;
41
import org.bouncycastle.asn1.x509.AccessDescription;
42
import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
43
import org.bouncycastle.asn1.x509.Extension;
44
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
45

46
/**
47
 * @since 5.0
48
 */
49
class CertificateUtils {
50

51
    private CertificateUtils() {
52
    }
53

54
    /**
55
     * Load the certificate chain from the specified PKCS#7 files.
56
     */
57
    public static Certificate[] loadCertificateChain(File file) throws IOException, CertificateException {
58
        try (FileInputStream in = new FileInputStream(file)) {
1✔
59
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
1✔
60
            Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(in);
1✔
61
            List<X509Certificate> list = (List) new ArrayList<>(certificates);
1✔
62
            list.sort(getChainComparator());
1✔
63
            return list.toArray(new Certificate[0]);
1✔
64
        }
65
    }
66

67
    /**
68
     * Returns a comparator that sorts the certificates in the chain in the order of the certification path,
69
     * from the end-entity certificate to the root CA.
70
     */
71
    public static Comparator<X509Certificate> getChainComparator() {
72
        return Comparator.comparing(X509Certificate::getBasicConstraints)
1✔
73
                .thenComparing(X509Certificate::getNotBefore, Comparator.reverseOrder())
1✔
74
                .thenComparing(X509Certificate::getSubjectX500Principal, Comparator.comparing(X500Principal::getName));
1✔
75
    }
76

77
    /**
78
     * Returns the authority information access extension of the specified certificate.
79
     *
80
     * @since 7.0
81
     */
82
    public static AuthorityInformationAccess getAuthorityInformationAccess(X509Certificate certificate) {
83
        byte[] aia = certificate.getExtensionValue(Extension.authorityInfoAccess.getId());
1✔
84
        return aia != null ? AuthorityInformationAccess.getInstance(ASN1OctetString.getInstance(aia).getOctets()) : null;
1✔
85
    }
86

87
    /**
88
     * Returns the issuer certificate URL of the specified certificate (HTTP only).
89
     *
90
     * @since 7.0
91
     */
92
    public static String getIssuerCertificateURL(X509Certificate certificate) {
93
        AuthorityInformationAccess aia = getAuthorityInformationAccess(certificate);
1✔
94
        if (aia != null) {
1✔
95
            for (AccessDescription access : aia.getAccessDescriptions()) {
1✔
96
                if (X509ObjectIdentifiers.id_ad_caIssuers.equals(access.getAccessMethod())) {
1✔
97
                    String url = access.getAccessLocation().getName().toString();
1✔
98
                    if (url.startsWith("http")) {
1✔
99
                        return access.getAccessLocation().getName().toString();
1✔
100
                    }
101
                }
102
            }
103
        }
104

105
        return null;
1✔
106
    }
107

108
    /**
109
     * Returns the issuer certificates of the specified certificate. Multiple issuer certificates may be returned
110
     * if the certificate is cross-signed.
111
     *
112
     * @since 7.0
113
     */
114
    public static Collection<X509Certificate> getIssuerCertificates(X509Certificate certificate) throws IOException, CertificateException {
115
        String certificateURL = getIssuerCertificateURL(certificate);
1✔
116
        if (certificateURL != null) {
1✔
117
            File cacheDirectory = new File(OSUtils.getCacheDirectory("jsign"), "certificates");
1✔
118
            HttpClient cache = new HttpClient(cacheDirectory, 90 * 24 * 3600 * 1000L);
1✔
119
            try (InputStream in = cache.getInputStream(new URL(certificateURL))) {
1✔
120
                CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
1✔
121
                return (Collection) certificateFactory.generateCertificates(in);
1✔
122
            }
123
        }
124

125
        return Collections.emptyList();
×
126
    }
127

128
    /**
129
     * Returns the certificate chain of the specified certificate up to the specified depth.
130
     *
131
     * @since 7.0
132
     */
133
    public static Collection<X509Certificate> getCertificateChain(X509Certificate certificate, int maxDepth) {
134
        List<X509Certificate> chain = new ArrayList<>();
1✔
135
        chain.add(certificate);
1✔
136

137
        if (maxDepth > 0 && !isSelfSigned(certificate)) {
1✔
138
            try {
139
                Collection<X509Certificate> issuers = getIssuerCertificates(certificate);
1✔
140
                for (X509Certificate issuer : issuers) {
1✔
141
                    chain.addAll(getCertificateChain(issuer, maxDepth - 1));
1✔
142
                }
1✔
143
            } catch (Exception e) {
×
144
                e.printStackTrace();
×
145
            }
1✔
146
        }
147

148
        return chain;
1✔
149
    }
150

151
    /**
152
     * Tells if the specified certificate is self-signed.
153
     *
154
     * @since 7.0
155
     */
156
    public static boolean isSelfSigned(X509Certificate certificate) {
157
        return certificate.getSubjectDN().equals(certificate.getIssuerDN());
1✔
158
    }
159

160
    /**
161
     * Completes the specified chain with the missing issuer certificates.
162
     *
163
     * @since 7.0
164
     */
165
    public static List<X509Certificate> getFullCertificateChain(Collection<X509Certificate> chain) {
166
        Set<String> missingIssuerNames = chain.stream().map(c -> c.getIssuerX500Principal().getName())
1✔
167
                .collect(Collectors.toCollection(LinkedHashSet::new));
1✔
168
        for (X509Certificate certificate : chain) {
1✔
169
            missingIssuerNames.remove(certificate.getSubjectX500Principal().getName());
1✔
170
        }
1✔
171
        Set<X509Certificate> orphanCertificates = new HashSet<>();
1✔
172
        for (X509Certificate certificate : chain) {
1✔
173
            if (missingIssuerNames.contains(certificate.getIssuerX500Principal().getName())) {
1✔
174
                orphanCertificates.add(certificate);
1✔
175
            }
176
        }
1✔
177

178
        List<X509Certificate> fullChain = new ArrayList<>(chain);
1✔
179
        for (X509Certificate orphanCertificate : orphanCertificates) {
1✔
180
            fullChain.remove(orphanCertificate);
1✔
181
            fullChain.addAll(getCertificateChain(orphanCertificate, 10));
1✔
182
        }
1✔
183

184
        return fullChain;
1✔
185
    }
186
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc