2323import java .security .cert .CertificateParsingException ;
2424import java .security .cert .X509Certificate ;
2525import java .util .*;
26- import java .util .function .BiFunction ;
27- import java .util .stream .Collectors ;
2826
2927/**
3028 * Utility to extract information from X509 certificates.
3129 *
32- * @since 5.7 .0
30+ * @since 4.11 .0
3331 */
3432public class TlsUtils {
3533
@@ -47,25 +45,53 @@ public class TlsUtils {
4745 put ("1.3.6.1.5.5.7.3.8" , "Binding the hash of an object to a time from an agreed-upon time" );
4846 }});
4947 private static String PARSING_ERROR = "<parsing-error>" ;
50- private static final Map <String , BiFunction <byte [], X509Certificate , String >> EXTENSIONS = Collections .unmodifiableMap (
51- new HashMap <String , BiFunction <byte [], X509Certificate , String >>() {{
52- put ("2.5.29.14" , (v , c ) -> "SubjectKeyIdentifier = " + octetStringHexDump (v ));
53- put ("2.5.29.15" , (v , c ) -> "KeyUsage = " + keyUsageBitString (c .getKeyUsage (), v ));
54- put ("2.5.29.16" , (v , c ) -> "PrivateKeyUsage = " + hexDump (0 , v ));
55- put ("2.5.29.17" , (v , c ) -> {
56- try {
57- return "SubjectAlternativeName = " + sans (c , "/" );
58- } catch (CertificateParsingException e ) {
59- return "SubjectAlternativeName = " + PARSING_ERROR ;
48+ private static final Map <String , ExtensionStringConverter > EXTENSIONS = Collections .unmodifiableMap (
49+ new HashMap <String , ExtensionStringConverter >() {{
50+ put ("2.5.29.14" , new ExtensionStringConverter () {
51+ @ Override
52+ public String convert (byte [] derOctetString , X509Certificate certificate ) {
53+ return "SubjectKeyIdentifier = " + octetStringHexDump (derOctetString );
54+ }
55+ });
56+ put ("2.5.29.15" , new ExtensionStringConverter () {
57+ @ Override
58+ public String convert (byte [] derOctetString , X509Certificate certificate ) {
59+ return "KeyUsage = " + keyUsageBitString (certificate .getKeyUsage (), derOctetString );
60+ }
61+ });
62+ put ("2.5.29.16" , new HexDumpConverter ("PrivateKeyUsage" ));
63+ put ("2.5.29.17" , new ExtensionStringConverter () {
64+ @ Override
65+ public String convert (byte [] derOctetString , X509Certificate certificate ) {
66+ try {
67+ return "SubjectAlternativeName = " + sans (certificate , "/" );
68+ } catch (CertificateParsingException e ) {
69+ return "SubjectAlternativeName = " + PARSING_ERROR ;
70+ }
71+ }
72+ });
73+ put ("2.5.29.18" , new HexDumpConverter ("IssuerAlternativeName" ));
74+ put ("2.5.29.19" , new ExtensionStringConverter () {
75+ @ Override
76+ public String convert (byte [] derOctetString , X509Certificate certificate ) {
77+ return "BasicConstraints = " + basicConstraints (derOctetString );
78+ }
79+ });
80+ put ("2.5.29.30" , new HexDumpConverter ("NameConstraints" ));
81+ put ("2.5.29.33" , new HexDumpConverter ("PolicyMappings" ));
82+ put ("2.5.29.35" , new ExtensionStringConverter () {
83+ @ Override
84+ public String convert (byte [] derOctetString , X509Certificate certificate ) {
85+ return "AuthorityKeyIdentifier = " + authorityKeyIdentifier (derOctetString );
86+ }
87+ });
88+ put ("2.5.29.36" , new HexDumpConverter ("PolicyConstraints" ));
89+ put ("2.5.29.37" , new ExtensionStringConverter () {
90+ @ Override
91+ public String convert (byte [] derOctetString , X509Certificate certificate ) {
92+ return "ExtendedKeyUsage = " + extendedKeyUsage (derOctetString , certificate );
6093 }
6194 });
62- put ("2.5.29.18" , (v , c ) -> "IssuerAlternativeName = " + hexDump (0 , v ));
63- put ("2.5.29.19" , (v , c ) -> "BasicConstraints = " + basicConstraints (v ));
64- put ("2.5.29.30" , (v , c ) -> "NameConstraints = " + hexDump (0 , v ));
65- put ("2.5.29.33" , (v , c ) -> "PolicyMappings = " + hexDump (0 , v ));
66- put ("2.5.29.35" , (v , c ) -> "AuthorityKeyIdentifier = " + authorityKeyIdentifier (v ));
67- put ("2.5.29.36" , (v , c ) -> "PolicyConstraints = " + hexDump (0 , v ));
68- put ("2.5.29.37" , (v , c ) -> "ExtendedKeyUsage = " + extendedKeyUsage (v , c ));
6995 }});
7096
7197 /**
@@ -96,7 +122,7 @@ public static void logPeerCertificateInfo(SSLSession session) {
96122 * Get a string representation of certificate info.
97123 *
98124 * @param certificate the certificate to analyze
99- * @param prefix the line prefix
125+ * @param prefix the line prefix
100126 * @return information about the certificate
101127 */
102128 public static String peerCertificateInfo (Certificate certificate , String prefix ) {
@@ -112,13 +138,12 @@ public static String peerCertificateInfo(Certificate certificate, String prefix)
112138 }
113139
114140 private static String sans (X509Certificate c , String separator ) throws CertificateParsingException {
115- return String .join (separator , Optional .ofNullable (c .getSubjectAlternativeNames ())
116- .orElse (new ArrayList <>())
117- .stream ()
118- .map (v -> v .toString ())
119- .collect (Collectors .toList ()));
141+ Collection <List <?>> sans = c .getSubjectAlternativeNames ();
142+ sans = sans == null ? new ArrayList <List <?>>() : sans ;
143+ return join (separator , new ArrayList <List <?>>(sans ));
120144 }
121145
146+
122147 /**
123148 * Human-readable representation of an X509 certificate extension.
124149 * <p>
@@ -129,31 +154,34 @@ private static String sans(X509Certificate c, String separator) throws Certifica
129154 * other DER-encoded objects, making a comprehensive support in this utility
130155 * impossible.
131156 *
132- * @param oid extension OID
157+ * @param oid extension OID
133158 * @param derOctetString the extension value as a DER octet string
134- * @param certificate the certificate
159+ * @param certificate the certificate
135160 * @return the OID and the value
136161 * @see <a href="http://luca.ntop.org/Teaching/Appunti/asn1.html">A Layman's Guide to a Subset of ASN.1, BER, and DER</a>
137162 * @see <a href="https://docs.microsoft.com/en-us/windows/desktop/seccertenroll/about-der-encoding-of-asn-1-types">DER Encoding of ASN.1 Types</a>
138163 */
139164 public static String extensionPrettyPrint (String oid , byte [] derOctetString , X509Certificate certificate ) {
140165 try {
141- return EXTENSIONS .getOrDefault (oid , (v , c ) -> oid + " = " + hexDump (0 , derOctetString ))
142- .apply (derOctetString , certificate );
166+ ExtensionStringConverter converter = EXTENSIONS .get (oid );
167+ if (converter == null ) {
168+ converter = new HexDumpConverter (oid );
169+ }
170+ return converter .convert (derOctetString , certificate );
143171 } catch (Exception e ) {
144172 return oid + " = " + PARSING_ERROR ;
145173 }
146174 }
147175
148176 private static String extensions (X509Certificate certificate ) {
149- List <String > extensions = new ArrayList <>();
177+ List <String > extensions = new ArrayList <String >();
150178 for (String oid : certificate .getCriticalExtensionOIDs ()) {
151179 extensions .add (extensionPrettyPrint (oid , certificate .getExtensionValue (oid ), certificate ) + " (critical)" );
152180 }
153181 for (String oid : certificate .getNonCriticalExtensionOIDs ()) {
154182 extensions .add (extensionPrettyPrint (oid , certificate .getExtensionValue (oid ), certificate ) + " (non-critical)" );
155183 }
156- return String . join (", " , extensions );
184+ return join (", " , extensions );
157185 }
158186
159187 private static String octetStringHexDump (byte [] derOctetString ) {
@@ -166,22 +194,22 @@ private static String octetStringHexDump(byte[] derOctetString) {
166194 }
167195
168196 private static String hexDump (int start , byte [] derOctetString ) {
169- List <String > hexs = new ArrayList <>();
197+ List <String > hexs = new ArrayList <String >();
170198 for (int i = start ; i < derOctetString .length ; i ++) {
171199 hexs .add (String .format ("%02X" , derOctetString [i ]));
172200 }
173- return String . join (":" , hexs );
201+ return join (":" , hexs );
174202 }
175203
176204 private static String keyUsageBitString (boolean [] keyUsage , byte [] derOctetString ) {
177205 if (keyUsage != null ) {
178- List <String > usage = new ArrayList <>();
206+ List <String > usage = new ArrayList <String >();
179207 for (int i = 0 ; i < keyUsage .length ; i ++) {
180208 if (keyUsage [i ]) {
181209 usage .add (KEY_USAGE .get (i ));
182210 }
183211 }
184- return String . join ("/" , usage );
212+ return join ("/" , usage );
185213 } else {
186214 return hexDump (0 , derOctetString );
187215 }
@@ -217,13 +245,49 @@ private static String extendedKeyUsage(byte[] derOctetString, X509Certificate ce
217245 if (extendedKeyUsage == null ) {
218246 return hexDump (0 , derOctetString );
219247 } else {
220- return String .join ("/" , extendedKeyUsage .stream ()
221- .map (oid -> EXTENDED_KEY_USAGE .getOrDefault (oid , oid ))
222- .collect (Collectors .toList ()));
248+ List <String > extendedKeyUsageLiteral = new ArrayList <String >();
249+ for (String oid : extendedKeyUsage ) {
250+ String literal = EXTENDED_KEY_USAGE .get (oid );
251+ extendedKeyUsageLiteral .add (literal == null ? oid : literal );
252+ }
253+ return join ("/" , extendedKeyUsageLiteral );
223254 }
224255 } catch (CertificateParsingException e ) {
225256 return PARSING_ERROR ;
226257 }
227258 }
228259
260+ private static String join (String separator , List <?> items ) {
261+ StringBuilder builder = new StringBuilder ();
262+ if (items != null ) {
263+ for (int i = 0 ; i < items .size (); i ++) {
264+ if (i > 0 ) {
265+ builder .append (separator );
266+ }
267+ builder .append (items .get (i ));
268+ }
269+ }
270+ return builder .toString ();
271+ }
272+
273+ private interface ExtensionStringConverter {
274+
275+ String convert (byte [] derOctetString , X509Certificate certificate );
276+
277+ }
278+
279+ private static class HexDumpConverter implements ExtensionStringConverter {
280+
281+ private final String extension ;
282+
283+ private HexDumpConverter (String extension ) {
284+ this .extension = extension ;
285+ }
286+
287+ @ Override
288+ public String convert (byte [] derOctetString , X509Certificate certificate ) {
289+ return extension + " = " + hexDump (0 , derOctetString );
290+ }
291+ }
292+
229293}
0 commit comments