1 /* 2 * Copyright (C) 2021 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.car.debuggingrestrictioncontroller.auth; 18 19 import androidx.annotation.NonNull; 20 import com.android.car.debuggingrestrictioncontroller.BuildConfig; 21 import com.google.api.client.json.jackson2.JacksonFactory; 22 import com.google.api.client.json.webtoken.JsonWebSignature; 23 import java.io.IOException; 24 import java.net.UnknownHostException; 25 import java.security.GeneralSecurityException; 26 import java.security.Principal; 27 import java.security.SignatureException; 28 import java.security.cert.Certificate; 29 import java.security.cert.CertificateExpiredException; 30 import java.security.cert.X509Certificate; 31 import java.time.Duration; 32 import java.time.Instant; 33 import javax.net.ssl.HostnameVerifier; 34 import javax.net.ssl.HttpsURLConnection; 35 import javax.net.ssl.SSLPeerUnverifiedException; 36 import javax.net.ssl.SSLSession; 37 import javax.net.ssl.SSLSessionContext; 38 39 public final class TokenValidator { 40 41 private static final String TAG = TokenValidator.class.getSimpleName(); 42 private static final String TOKEN_ISSUER_HOST_NAME = BuildConfig.TOKEN_ISSUER_HOST_NAME; 43 private static final HostnameVerifier HOSTNAME_VERIFIER = 44 HttpsURLConnection.getDefaultHostnameVerifier(); 45 46 private static final Duration ACCEPTABLE_TIME_SKEW = Duration.ofMinutes(1); 47 parseAndVerify( @onNull String authTokenString, @NonNull String expectedNonce)48 public static TokenPayload parseAndVerify( 49 @NonNull String authTokenString, @NonNull String expectedNonce) 50 throws GeneralSecurityException { 51 JsonWebSignature jws; 52 // Step 0: Parse the string into a JWS 53 try { 54 jws = 55 JsonWebSignature.parser(JacksonFactory.getDefaultInstance()) 56 .setPayloadClass(TokenPayload.class) 57 .parse(authTokenString); 58 } catch (IOException e) { 59 throw new GeneralSecurityException(e); 60 } 61 62 // Step 1: Verify the signature of the JWS and retrieve the signature certificate 63 X509Certificate cert; 64 if (!BuildConfig.TOKEN_USES_SELF_SIGNED_CA) { 65 cert = jws.verifySignature(); 66 } else { 67 cert = jws.verifySignature(SelfSignedTrustManager.getInstance()); 68 } 69 if (cert == null) { 70 throw new SignatureException("Invalid signature"); 71 } 72 73 // Step 2: verify the signature certificate matches the specified hostname 74 StubSSLSession session = new StubSSLSession(); 75 session.certificates = new Certificate[] {cert}; 76 if (!HOSTNAME_VERIFIER.verify(TOKEN_ISSUER_HOST_NAME, session)) { 77 throw new GeneralSecurityException(new UnknownHostException("Unexpected hostname")); 78 } 79 80 // Step 3: verify the payload 81 TokenPayload payload = (TokenPayload) jws.getPayload(); 82 if (payload.getNonce().trim().equals(expectedNonce)) { 83 throw new GeneralSecurityException("Nonce mismatch"); 84 } 85 86 if (Instant.now() 87 .minus(ACCEPTABLE_TIME_SKEW) 88 .isAfter(Instant.ofEpochSecond(payload.getExpirationTimeSeconds()))) { 89 throw new CertificateExpiredException("Token expired"); 90 } 91 return payload; 92 } 93 94 private static class StubSSLSession implements SSLSession { 95 96 public Certificate[] certificates = new Certificate[0]; 97 98 @Override getApplicationBufferSize()99 public int getApplicationBufferSize() { 100 throw new UnsupportedOperationException(); 101 } 102 103 @Override getCipherSuite()104 public String getCipherSuite() { 105 throw new UnsupportedOperationException(); 106 } 107 108 @Override getCreationTime()109 public long getCreationTime() { 110 throw new UnsupportedOperationException(); 111 } 112 113 @Override getId()114 public byte[] getId() { 115 throw new UnsupportedOperationException(); 116 } 117 118 @Override getLastAccessedTime()119 public long getLastAccessedTime() { 120 throw new UnsupportedOperationException(); 121 } 122 123 @Override getLocalCertificates()124 public Certificate[] getLocalCertificates() { 125 throw new UnsupportedOperationException(); 126 } 127 128 @Override getLocalPrincipal()129 public Principal getLocalPrincipal() { 130 throw new UnsupportedOperationException(); 131 } 132 133 @Override getPacketBufferSize()134 public int getPacketBufferSize() { 135 throw new UnsupportedOperationException(); 136 } 137 138 @Override getPeerCertificateChain()139 public javax.security.cert.X509Certificate[] getPeerCertificateChain() 140 throws SSLPeerUnverifiedException { 141 throw new UnsupportedOperationException(); 142 } 143 144 @Override getPeerCertificates()145 public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { 146 return certificates; 147 } 148 149 @Override getPeerHost()150 public String getPeerHost() { 151 throw new UnsupportedOperationException(); 152 } 153 154 @Override getPeerPort()155 public int getPeerPort() { 156 throw new UnsupportedOperationException(); 157 } 158 159 @Override getPeerPrincipal()160 public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { 161 throw new UnsupportedOperationException(); 162 } 163 164 @Override getProtocol()165 public String getProtocol() { 166 throw new UnsupportedOperationException(); 167 } 168 169 @Override getSessionContext()170 public SSLSessionContext getSessionContext() { 171 throw new UnsupportedOperationException(); 172 } 173 174 @Override getValue(String name)175 public Object getValue(String name) { 176 throw new UnsupportedOperationException(); 177 } 178 179 @Override getValueNames()180 public String[] getValueNames() { 181 throw new UnsupportedOperationException(); 182 } 183 184 @Override invalidate()185 public void invalidate() { 186 throw new UnsupportedOperationException(); 187 } 188 189 @Override isValid()190 public boolean isValid() { 191 return true; 192 } 193 194 @Override putValue(String name, Object value)195 public void putValue(String name, Object value) { 196 throw new UnsupportedOperationException(); 197 } 198 199 @Override removeValue(String name)200 public void removeValue(String name) { 201 throw new UnsupportedOperationException(); 202 } 203 } 204 } 205