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