1 /* 2 * Copyright (C) 2018 The Android Open Source Project 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 com.android.internal.net.ipsec.ike.message; 18 19 import android.annotation.IntDef; 20 import android.annotation.Nullable; 21 import android.net.ipsec.ike.exceptions.IkeProtocolException; 22 23 import com.android.internal.net.ipsec.ike.exceptions.AuthenticationFailedException; 24 25 import java.io.IOException; 26 import java.lang.annotation.Retention; 27 import java.lang.annotation.RetentionPolicy; 28 import java.nio.ByteBuffer; 29 import java.security.KeyStore; 30 import java.security.KeyStoreException; 31 import java.security.NoSuchAlgorithmException; 32 import java.security.ProviderException; 33 import java.security.cert.CertificateException; 34 import java.security.cert.TrustAnchor; 35 import java.security.cert.X509CRL; 36 import java.security.cert.X509Certificate; 37 import java.util.List; 38 import java.util.Set; 39 40 import javax.net.ssl.TrustManager; 41 import javax.net.ssl.TrustManagerFactory; 42 import javax.net.ssl.X509TrustManager; 43 44 /** 45 * IkeCertPayload is an abstract class that represents the common information for all Certificate 46 * Payload carrying different types of certifciate-related data and static methods related to 47 * certificate validation. 48 * 49 * <p>Certificate Payload is only sent in IKE_AUTH exchange. 50 * 51 * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.6">RFC 7296, Internet Key Exchange 52 * Protocol Version 2 (IKEv2)</a> 53 */ 54 public abstract class IkeCertPayload extends IkePayload { 55 // Length of certificate encoding type field in octets. 56 protected static final int CERT_ENCODING_LEN = 1; 57 58 private static final String KEY_STORE_TYPE_PKCS12 = "PKCS12"; 59 private static final String CERT_PATH_ALGO_PKIX = "PKIX"; 60 private static final String CERT_AUTH_TYPE_RSA = "RSA"; 61 62 @Retention(RetentionPolicy.SOURCE) 63 @IntDef({ 64 CERTIFICATE_ENCODING_X509_CERT_SIGNATURE, 65 CERTIFICATE_ENCODING_CRL, 66 CERTIFICATE_ENCODING_X509_CERT_HASH_URL, 67 }) 68 public @interface CertificateEncoding {} 69 70 public static final int CERTIFICATE_ENCODING_X509_CERT_SIGNATURE = 4; 71 public static final int CERTIFICATE_ENCODING_CRL = 7; 72 public static final int CERTIFICATE_ENCODING_X509_CERT_HASH_URL = 12; 73 74 @CertificateEncoding public final int certEncodingType; 75 IkeCertPayload(@ertificateEncoding int encodingType)76 protected IkeCertPayload(@CertificateEncoding int encodingType) { 77 this(false /*critical*/, encodingType); 78 } 79 IkeCertPayload(boolean critical, @CertificateEncoding int encodingType)80 protected IkeCertPayload(boolean critical, @CertificateEncoding int encodingType) { 81 super(PAYLOAD_TYPE_CERT, critical); 82 certEncodingType = encodingType; 83 } 84 getIkeCertPayload(boolean critical, byte[] payloadBody)85 protected static IkeCertPayload getIkeCertPayload(boolean critical, byte[] payloadBody) 86 throws IkeProtocolException { 87 ByteBuffer inputBuffer = ByteBuffer.wrap(payloadBody); 88 89 int certEncodingType = Byte.toUnsignedInt(inputBuffer.get()); 90 byte[] certData = new byte[payloadBody.length - CERT_ENCODING_LEN]; 91 inputBuffer.get(certData); 92 switch (certEncodingType) { 93 case CERTIFICATE_ENCODING_X509_CERT_SIGNATURE: 94 return new IkeCertX509CertPayload(critical, certData); 95 // TODO: Support decoding CRL and "Hash and URL". 96 case CERTIFICATE_ENCODING_CRL: 97 throw new AuthenticationFailedException( 98 "CERTIFICATE_ENCODING_CRL decoding is unsupported."); 99 case CERTIFICATE_ENCODING_X509_CERT_HASH_URL: 100 throw new AuthenticationFailedException( 101 "CERTIFICATE_ENCODING_X509_CERT_HASH_URL decoding is unsupported"); 102 default: 103 throw new AuthenticationFailedException("Unrecognized certificate encoding type."); 104 } 105 } 106 107 /** 108 * Validate an end certificate against the received chain and trust anchors. 109 * 110 * <p>Validation is done by checking if there is a valid certificate path from end certificate 111 * to provided trust anchors. 112 * 113 * <p>TrustManager implementation used in this method MUST conforms RFC 4158 and RFC 5280. As 114 * indicated in RFC 4158, Key Identifiers(KIDs) are not required to match during certification 115 * path validation and cannot be used to eliminate certificates. 116 * 117 * <p>Validation will fail if any certficate in the certificate chain is using RSA public key 118 * whose RSA modulus is smaller than 1024 bits. 119 * 120 * @param endCert the end certificate that will be used to verify AUTH payload 121 * @param certList all the received certificates (include the end certificate) 122 * @param crlList the certificate revocation lists 123 * @param trustAnchorSet the certificate authority set to validate the end certificate 124 * @throws AuthenticationFailedException if there is no valid certificate path 125 */ validateCertificates( X509Certificate endCert, List<X509Certificate> certList, @Nullable List<X509CRL> crlList, Set<TrustAnchor> trustAnchorSet)126 public static void validateCertificates( 127 X509Certificate endCert, 128 List<X509Certificate> certList, 129 @Nullable List<X509CRL> crlList, 130 Set<TrustAnchor> trustAnchorSet) 131 throws AuthenticationFailedException { 132 try { 133 // TODO: b/122676944 Support CRL checking 134 135 // By default, use system-trusted CAs 136 KeyStore keyStore = null; 137 138 // But if a specific trust anchor is specified, use that instead 139 if (trustAnchorSet != null && !trustAnchorSet.isEmpty()) { 140 keyStore = KeyStore.getInstance(KEY_STORE_TYPE_PKCS12); 141 keyStore.load(null); 142 for (TrustAnchor t : trustAnchorSet) { 143 X509Certificate trustedCert = t.getTrustedCert(); 144 String alias = 145 trustedCert.getSubjectX500Principal().getName() 146 + trustedCert.hashCode(); 147 keyStore.setCertificateEntry(alias, trustedCert); 148 } 149 } 150 151 // Build X509TrustManager with all keystore 152 TrustManagerFactory tmFactory = 153 TrustManagerFactory.getInstance( 154 CERT_PATH_ALGO_PKIX, IkeMessage.getTrustManagerProvider()); 155 tmFactory.init(keyStore); 156 157 X509TrustManager trustManager = null; 158 for (TrustManager tm : tmFactory.getTrustManagers()) { 159 if (tm instanceof X509TrustManager) { 160 trustManager = (X509TrustManager) tm; 161 } 162 } 163 if (trustManager == null) { 164 throw new ProviderException( 165 "X509TrustManager is not supported by " 166 + IkeMessage.getTrustManagerProvider().getName()); 167 } 168 169 // Build and validate certificate path 170 trustManager.checkServerTrusted( 171 certList.toArray(new X509Certificate[certList.size()]), CERT_AUTH_TYPE_RSA); 172 } catch (NoSuchAlgorithmException e) { 173 throw new ProviderException("Algorithm is not supported by the provider", e); 174 } catch (IOException | KeyStoreException e) { 175 throw new IllegalStateException(e); 176 } catch (CertificateException e) { 177 throw new AuthenticationFailedException(e); 178 } 179 } 180 } 181