View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. 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, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  package org.jboss.netty.handler.ssl.util;
18  
19  import org.jboss.netty.buffer.ChannelBuffers;
20  import org.jboss.netty.util.internal.EmptyArrays;
21  
22  import javax.net.ssl.ManagerFactoryParameters;
23  import javax.net.ssl.TrustManager;
24  import javax.net.ssl.TrustManagerFactory;
25  import javax.net.ssl.X509TrustManager;
26  import java.security.KeyStore;
27  import java.security.MessageDigest;
28  import java.security.NoSuchAlgorithmException;
29  import java.security.cert.CertificateEncodingException;
30  import java.security.cert.CertificateException;
31  import java.security.cert.X509Certificate;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.List;
35  import java.util.regex.Pattern;
36  
37  /**
38   * An {@link TrustManagerFactory} that trusts an X.509 certificate whose SHA1 checksum matches.
39   * <p>
40   * <strong>NOTE:</strong>
41   * Never use this {@link TrustManagerFactory} in production unless you are not sure what you are exactly doing with it.
42   * </p><p>
43   * The SHA1 checksum of an X.509 certificate is calculated from its DER encoded format.  You can get the fingerprint of
44   * an X.509 certificate using the {@code openssl} command.  For example:
45   * <pre>
46   * $ openssl x509 -fingerprint -sha1 -in my_certificate.crt
47   * SHA1 Fingerprint=4E:85:10:55:BC:7B:12:08:D1:EA:0A:12:C9:72:EE:F3:AA:B2:C7:CB
48   * -----BEGIN CERTIFICATE-----
49   * MIIBqjCCAROgAwIBAgIJALiT3Nvp0kvmMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV
50   * BAMTC2V4YW1wbGUuY29tMCAXDTcwMDEwMTAwMDAwMFoYDzk5OTkxMjMxMjM1OTU5
51   * WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
52   * gYkCgYEAnadvODG0QCiHhaFZlLHtr5gLIkDQS8ErZ//KfqeCHTC/KJsl3xYFk0zG
53   * aCv2FcmkOlokm77qV8qOW2DZdND7WuYzX6nLVuLb+GYxZ7b45iMAbAajvGh8jc9U
54   * o07fUIahGqTDAIAGCWsoLUOQ9nMzO/8GRHcXJAeQ2MGY2VpCcv0CAwEAATANBgkq
55   * hkiG9w0BAQUFAAOBgQBpRCnmjmNM0D7yrpkUJpBTNiqinhKLbeOvPWm+YmdInUUs
56   * LoMu0mZ1IANemLwqbwJJ76fknngeB+YuVAj46SurvVCV6ekwHcbgpW1u063IRwKk
57   * tQhOBO0HQxldUS4+4MYv/kuvnKkbjfgh5qfWw89Kx4kD+cycpP4yPtgDGk8ZMA==
58   * -----END CERTIFICATE-----
59   * </pre>
60   * </p>
61   */
62  public final class FingerprintTrustManagerFactory extends SimpleTrustManagerFactory {
63  
64      private static final Pattern FINGERPRINT_PATTERN = Pattern.compile("^[0-9a-fA-F:]+$");
65      private static final Pattern FINGERPRINT_STRIP_PATTERN = Pattern.compile(":");
66      private static final int SHA1_BYTE_LEN = 20;
67      private static final int SHA1_HEX_LEN = SHA1_BYTE_LEN * 2;
68  
69      private static final ThreadLocal<MessageDigest> tlmd = new ThreadLocal<MessageDigest>() {
70          @Override
71          protected MessageDigest initialValue() {
72              try {
73                  return MessageDigest.getInstance("SHA1");
74              } catch (NoSuchAlgorithmException e) {
75                  // All Java implementation must have SHA1 digest algorithm.
76                  throw new Error(e);
77              }
78          }
79      };
80  
81      private final TrustManager tm = new X509TrustManager() {
82  
83          public void checkClientTrusted(X509Certificate[] chain, String s) throws CertificateException {
84              checkTrusted("client", chain);
85          }
86  
87          public void checkServerTrusted(X509Certificate[] chain, String s) throws CertificateException {
88              checkTrusted("server", chain);
89          }
90  
91          private void checkTrusted(String type, X509Certificate[] chain) throws CertificateException {
92              X509Certificate cert = chain[0];
93              byte[] fingerprint = fingerprint(cert);
94              boolean found = false;
95              for (byte[] allowedFingerprint: fingerprints) {
96                  if (Arrays.equals(fingerprint, allowedFingerprint)) {
97                      found = true;
98                      break;
99                  }
100             }
101 
102             if (!found) {
103                 throw new CertificateException(
104                         type + " certificate with unknown fingerprint: " + cert.getSubjectDN());
105             }
106         }
107 
108         private byte[] fingerprint(X509Certificate cert) throws CertificateEncodingException {
109             MessageDigest md = tlmd.get();
110             md.reset();
111             return md.digest(cert.getEncoded());
112         }
113 
114         public X509Certificate[] getAcceptedIssuers() {
115             return EmptyArrays.EMPTY_X509_CERTIFICATES;
116         }
117     };
118 
119     private final byte[][] fingerprints;
120 
121     /**
122      * Creates a new instance.
123      *
124      * @param fingerprints a list of SHA1 fingerprints in heaxdecimal form
125      */
126     public FingerprintTrustManagerFactory(Iterable<String> fingerprints) {
127         this(toFingerprintArray(fingerprints));
128     }
129 
130     /**
131      * Creates a new instance.
132      *
133      * @param fingerprints a list of SHA1 fingerprints in heaxdecimal form
134      */
135     public FingerprintTrustManagerFactory(String... fingerprints) {
136         this(toFingerprintArray(Arrays.asList(fingerprints)));
137     }
138 
139     /**
140      * Creates a new instance.
141      *
142      * @param fingerprints a list of SHA1 fingerprints
143      */
144     public FingerprintTrustManagerFactory(byte[]... fingerprints) {
145         if (fingerprints == null) {
146             throw new NullPointerException("fingerprints");
147         }
148 
149         List<byte[]> list = new ArrayList<byte[]>();
150         for (byte[] f: fingerprints) {
151             if (f == null) {
152                 break;
153             }
154             if (f.length != SHA1_BYTE_LEN) {
155                 throw new IllegalArgumentException("malformed fingerprint: " +
156                         ChannelBuffers.hexDump(ChannelBuffers.wrappedBuffer(f)) + " (expected: SHA1)");
157             }
158             list.add(f.clone());
159         }
160 
161         this.fingerprints = list.toArray(new byte[list.size()][]);
162     }
163 
164     private static byte[][] toFingerprintArray(Iterable<String> fingerprints) {
165         if (fingerprints == null) {
166             throw new NullPointerException("fingerprints");
167         }
168 
169         List<byte[]> list = new ArrayList<byte[]>();
170         for (String f: fingerprints) {
171             if (f == null) {
172                 break;
173             }
174 
175             if (!FINGERPRINT_PATTERN.matcher(f).matches()) {
176                 throw new IllegalArgumentException("malformed fingerprint: " + f);
177             }
178             f = FINGERPRINT_STRIP_PATTERN.matcher(f).replaceAll("");
179             if (f.length() != SHA1_HEX_LEN) {
180                 throw new IllegalArgumentException("malformed fingerprint: " + f + " (expected: SHA1)");
181             }
182 
183             byte[] farr = new byte[SHA1_BYTE_LEN];
184             for (int i = 0; i < farr.length; i ++) {
185                 int strIdx = i << 1;
186                 farr[i] = (byte) Integer.parseInt(f.substring(strIdx, strIdx + 2), 16);
187             }
188         }
189 
190         return list.toArray(new byte[list.size()][]);
191     }
192 
193     @Override
194     protected void engineInit(KeyStore keyStore) throws Exception { }
195 
196     @Override
197     protected void engineInit(ManagerFactoryParameters managerFactoryParameters) throws Exception { }
198 
199     @Override
200     protected TrustManager[] engineGetTrustManagers() {
201         return new TrustManager[] { tm };
202     }
203 }