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