1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.netty.handler.ssl;
18
19 import io.netty.util.internal.PlatformDependent;
20 import io.netty.util.internal.logging.InternalLogger;
21 import io.netty.util.internal.logging.InternalLoggerFactory;
22
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.concurrent.ConcurrentMap;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
28
29
30
31
32
33
34 final class CipherSuiteConverter {
35
36 private static final InternalLogger logger = InternalLoggerFactory.getInstance(CipherSuiteConverter.class);
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 private static final Pattern JAVA_CIPHERSUITE_PATTERN =
52 Pattern.compile("^(?:TLS|SSL)_((?:(?!_WITH_).)+)_WITH_(.*)_(.*)$");
53
54
55
56
57
58
59
60
61
62
63
64
65
66 private static final Pattern OPENSSL_CIPHERSUITE_PATTERN =
67
68 Pattern.compile(
69 "^(?:(" +
70 "(?:(?:EXP-)?" +
71 "(?:" +
72 "(?:DHE|EDH|ECDH|ECDHE|SRP|RSA)-(?:DSS|RSA|ECDSA|PSK)|" +
73 "(?:ADH|AECDH|KRB5|PSK|SRP)" +
74 ')' +
75 ")|" +
76 "EXP" +
77 ")-)?" +
78 "(.*)-(.*)$");
79
80 private static final Pattern JAVA_AES_CBC_PATTERN = Pattern.compile("^(AES)_([0-9]+)_CBC$");
81 private static final Pattern JAVA_AES_PATTERN = Pattern.compile("^(AES)_([0-9]+)_(.*)$");
82 private static final Pattern OPENSSL_AES_CBC_PATTERN = Pattern.compile("^(AES)([0-9]+)$");
83 private static final Pattern OPENSSL_AES_PATTERN = Pattern.compile("^(AES)([0-9]+)-(.*)$");
84
85
86
87
88
89 private static final ConcurrentMap<String, String> j2o = PlatformDependent.newConcurrentHashMap();
90
91
92
93
94
95
96 private static final ConcurrentMap<String, Map<String, String>> o2j = PlatformDependent.newConcurrentHashMap();
97
98
99
100
101 static void clearCache() {
102 j2o.clear();
103 o2j.clear();
104 }
105
106
107
108
109 static boolean isJ2OCached(String key, String value) {
110 return value.equals(j2o.get(key));
111 }
112
113
114
115
116 static boolean isO2JCached(String key, String protocol, String value) {
117 Map<String, String> p2j = o2j.get(key);
118 if (p2j == null) {
119 return false;
120 } else {
121 return value.equals(p2j.get(protocol));
122 }
123 }
124
125
126
127
128 static String toOpenSsl(Iterable<String> javaCipherSuites) {
129 final StringBuilder buf = new StringBuilder();
130 for (String c: javaCipherSuites) {
131 if (c == null) {
132 break;
133 }
134
135 String converted = toOpenSsl(c);
136 if (converted != null) {
137 c = converted;
138 }
139
140 buf.append(c);
141 buf.append(':');
142 }
143
144 if (buf.length() > 0) {
145 buf.setLength(buf.length() - 1);
146 return buf.toString();
147 } else {
148 return "";
149 }
150 }
151
152
153
154
155
156
157 static String toOpenSsl(String javaCipherSuite) {
158 String converted = j2o.get(javaCipherSuite);
159 if (converted != null) {
160 return converted;
161 } else {
162 return cacheFromJava(javaCipherSuite);
163 }
164 }
165
166 private static String cacheFromJava(String javaCipherSuite) {
167 String openSslCipherSuite = toOpenSslUncached(javaCipherSuite);
168 if (openSslCipherSuite == null) {
169 return null;
170 }
171
172
173 j2o.putIfAbsent(javaCipherSuite, openSslCipherSuite);
174
175
176 final String javaCipherSuiteSuffix = javaCipherSuite.substring(4);
177 Map<String, String> p2j = new HashMap<String, String>(4);
178 p2j.put("", javaCipherSuiteSuffix);
179 p2j.put("SSL", "SSL_" + javaCipherSuiteSuffix);
180 p2j.put("TLS", "TLS_" + javaCipherSuiteSuffix);
181 o2j.put(openSslCipherSuite, p2j);
182
183 logger.debug("Cipher suite mapping: {} => {}", javaCipherSuite, openSslCipherSuite);
184
185 return openSslCipherSuite;
186 }
187
188 static String toOpenSslUncached(String javaCipherSuite) {
189 Matcher m = JAVA_CIPHERSUITE_PATTERN.matcher(javaCipherSuite);
190 if (!m.matches()) {
191 return null;
192 }
193
194 String handshakeAlgo = toOpenSslHandshakeAlgo(m.group(1));
195 String bulkCipher = toOpenSslBulkCipher(m.group(2));
196 String hmacAlgo = toOpenSslHmacAlgo(m.group(3));
197 if (handshakeAlgo.isEmpty()) {
198 return bulkCipher + '-' + hmacAlgo;
199 } else if (bulkCipher.contains("CHACHA20")) {
200 return handshakeAlgo + '-' + bulkCipher;
201 } else {
202 return handshakeAlgo + '-' + bulkCipher + '-' + hmacAlgo;
203 }
204 }
205
206 private static String toOpenSslHandshakeAlgo(String handshakeAlgo) {
207 final boolean export = handshakeAlgo.endsWith("_EXPORT");
208 if (export) {
209 handshakeAlgo = handshakeAlgo.substring(0, handshakeAlgo.length() - 7);
210 }
211
212 if ("RSA".equals(handshakeAlgo)) {
213 handshakeAlgo = "";
214 } else if (handshakeAlgo.endsWith("_anon")) {
215 handshakeAlgo = 'A' + handshakeAlgo.substring(0, handshakeAlgo.length() - 5);
216 }
217
218 if (export) {
219 if (handshakeAlgo.isEmpty()) {
220 handshakeAlgo = "EXP";
221 } else {
222 handshakeAlgo = "EXP-" + handshakeAlgo;
223 }
224 }
225
226 return handshakeAlgo.replace('_', '-');
227 }
228
229 private static String toOpenSslBulkCipher(String bulkCipher) {
230 if (bulkCipher.startsWith("AES_")) {
231 Matcher m = JAVA_AES_CBC_PATTERN.matcher(bulkCipher);
232 if (m.matches()) {
233 return m.replaceFirst("$1$2");
234 }
235
236 m = JAVA_AES_PATTERN.matcher(bulkCipher);
237 if (m.matches()) {
238 return m.replaceFirst("$1$2-$3");
239 }
240 }
241
242 if ("3DES_EDE_CBC".equals(bulkCipher)) {
243 return "DES-CBC3";
244 }
245
246 if ("RC4_128".equals(bulkCipher) || "RC4_40".equals(bulkCipher)) {
247 return "RC4";
248 }
249
250 if ("DES40_CBC".equals(bulkCipher) || "DES_CBC_40".equals(bulkCipher)) {
251 return "DES-CBC";
252 }
253
254 if ("RC2_CBC_40".equals(bulkCipher)) {
255 return "RC2-CBC";
256 }
257
258 return bulkCipher.replace('_', '-');
259 }
260
261 private static String toOpenSslHmacAlgo(String hmacAlgo) {
262
263
264
265
266
267
268 return hmacAlgo;
269 }
270
271
272
273
274
275
276
277 static String toJava(String openSslCipherSuite, String protocol) {
278 Map<String, String> p2j = o2j.get(openSslCipherSuite);
279 if (p2j == null) {
280 p2j = cacheFromOpenSsl(openSslCipherSuite);
281
282
283 if (p2j == null) {
284 return null;
285 }
286 }
287
288 String javaCipherSuite = p2j.get(protocol);
289 if (javaCipherSuite == null) {
290 javaCipherSuite = protocol + '_' + p2j.get("");
291 }
292
293 return javaCipherSuite;
294 }
295
296 private static Map<String, String> cacheFromOpenSsl(String openSslCipherSuite) {
297 String javaCipherSuiteSuffix = toJavaUncached(openSslCipherSuite);
298 if (javaCipherSuiteSuffix == null) {
299 return null;
300 }
301
302 final String javaCipherSuiteSsl = "SSL_" + javaCipherSuiteSuffix;
303 final String javaCipherSuiteTls = "TLS_" + javaCipherSuiteSuffix;
304
305
306 final Map<String, String> p2j = new HashMap<String, String>(4);
307 p2j.put("", javaCipherSuiteSuffix);
308 p2j.put("SSL", javaCipherSuiteSsl);
309 p2j.put("TLS", javaCipherSuiteTls);
310 o2j.putIfAbsent(openSslCipherSuite, p2j);
311
312
313 j2o.putIfAbsent(javaCipherSuiteTls, openSslCipherSuite);
314 j2o.putIfAbsent(javaCipherSuiteSsl, openSslCipherSuite);
315
316 logger.debug("Cipher suite mapping: {} => {}", javaCipherSuiteTls, openSslCipherSuite);
317 logger.debug("Cipher suite mapping: {} => {}", javaCipherSuiteSsl, openSslCipherSuite);
318
319 return p2j;
320 }
321
322 static String toJavaUncached(String openSslCipherSuite) {
323 Matcher m = OPENSSL_CIPHERSUITE_PATTERN.matcher(openSslCipherSuite);
324 if (!m.matches()) {
325 return null;
326 }
327
328 String handshakeAlgo = m.group(1);
329 final boolean export;
330 if (handshakeAlgo == null) {
331 handshakeAlgo = "";
332 export = false;
333 } else if (handshakeAlgo.startsWith("EXP-")) {
334 handshakeAlgo = handshakeAlgo.substring(4);
335 export = true;
336 } else if ("EXP".equals(handshakeAlgo)) {
337 handshakeAlgo = "";
338 export = true;
339 } else {
340 export = false;
341 }
342
343 handshakeAlgo = toJavaHandshakeAlgo(handshakeAlgo, export);
344 String bulkCipher = toJavaBulkCipher(m.group(2), export);
345 String hmacAlgo = toJavaHmacAlgo(m.group(3));
346
347 String javaCipherSuite = handshakeAlgo + "_WITH_" + bulkCipher + '_' + hmacAlgo;
348
349
350
351
352 return bulkCipher.contains("CHACHA20") ? javaCipherSuite + "_SHA256" : javaCipherSuite;
353 }
354
355 private static String toJavaHandshakeAlgo(String handshakeAlgo, boolean export) {
356 if (handshakeAlgo.isEmpty()) {
357 handshakeAlgo = "RSA";
358 } else if ("ADH".equals(handshakeAlgo)) {
359 handshakeAlgo = "DH_anon";
360 } else if ("AECDH".equals(handshakeAlgo)) {
361 handshakeAlgo = "ECDH_anon";
362 }
363
364 handshakeAlgo = handshakeAlgo.replace('-', '_');
365 if (export) {
366 return handshakeAlgo + "_EXPORT";
367 } else {
368 return handshakeAlgo;
369 }
370 }
371
372 private static String toJavaBulkCipher(String bulkCipher, boolean export) {
373 if (bulkCipher.startsWith("AES")) {
374 Matcher m = OPENSSL_AES_CBC_PATTERN.matcher(bulkCipher);
375 if (m.matches()) {
376 return m.replaceFirst("$1_$2_CBC");
377 }
378
379 m = OPENSSL_AES_PATTERN.matcher(bulkCipher);
380 if (m.matches()) {
381 return m.replaceFirst("$1_$2_$3");
382 }
383 }
384
385 if ("DES-CBC3".equals(bulkCipher)) {
386 return "3DES_EDE_CBC";
387 }
388
389 if ("RC4".equals(bulkCipher)) {
390 if (export) {
391 return "RC4_40";
392 } else {
393 return "RC4_128";
394 }
395 }
396
397 if ("DES-CBC".equals(bulkCipher)) {
398 if (export) {
399 return "DES_CBC_40";
400 } else {
401 return "DES_CBC";
402 }
403 }
404
405 if ("RC2-CBC".equals(bulkCipher)) {
406 if (export) {
407 return "RC2_CBC_40";
408 } else {
409 return "RC2_CBC";
410 }
411 }
412
413 return bulkCipher.replace('-', '_');
414 }
415
416 private static String toJavaHmacAlgo(String hmacAlgo) {
417
418
419
420
421
422
423 return hmacAlgo;
424 }
425
426 private CipherSuiteConverter() { }
427 }