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.server.wifi.util; 18 19 import android.net.wifi.util.HexEncoding; 20 import android.text.TextUtils; 21 import android.util.Log; 22 23 import java.nio.charset.StandardCharsets; 24 25 /** Utilities for parsing information from a certificate subject. */ 26 public class CertificateSubjectInfo { 27 private static final String TAG = "CertificateSubjectInfo"; 28 private static final String COMMON_NAME_PREFIX = "CN="; 29 private static final String ORGANIZATION_PREFIX = "O="; 30 private static final String LOCATION_PREFIX = "L="; 31 private static final String STATE_PREFIX = "ST="; 32 private static final String COUNTRY_PREFIX = "C="; 33 // This is hex-encoded string. 34 private static final String EMAILADDRESS_OID_PREFIX = "1.2.840.113549.1.9.1=#1614"; 35 36 public String rawData = ""; 37 public String commonName = ""; 38 public String organization = ""; 39 public String location = ""; 40 public String state = ""; 41 public String country = ""; 42 public String email = ""; 43 CertificateSubjectInfo()44 private CertificateSubjectInfo() { 45 } 46 47 /** 48 * Parse the subject of a certificate. 49 * 50 * @param subject the subject string 51 * @return CertificateSubjectInfo object if the subject is valid; otherwise, null. 52 */ parse(String subject)53 public static CertificateSubjectInfo parse(String subject) { 54 CertificateSubjectInfo info = new CertificateSubjectInfo(); 55 info.rawData = unescapeString(subject); 56 if (null == info.rawData) return null; 57 58 String[] parts = info.rawData.split(","); 59 for (String s : parts) { 60 if (s.startsWith(COMMON_NAME_PREFIX)) { 61 info.commonName = s.substring(COMMON_NAME_PREFIX.length()); 62 } else if (s.startsWith(ORGANIZATION_PREFIX)) { 63 info.organization = s.substring(ORGANIZATION_PREFIX.length()); 64 } else if (s.startsWith(LOCATION_PREFIX)) { 65 info.location = s.substring(LOCATION_PREFIX.length()); 66 } else if (s.startsWith(STATE_PREFIX)) { 67 info.state = s.substring(STATE_PREFIX.length()); 68 } else if (s.startsWith(COUNTRY_PREFIX)) { 69 info.country = s.substring(COUNTRY_PREFIX.length()); 70 } else if (s.startsWith(EMAILADDRESS_OID_PREFIX)) { 71 String hexStr = s.substring(EMAILADDRESS_OID_PREFIX.length()); 72 try { 73 info.email = new String( 74 HexEncoding.decode(hexStr.toCharArray(), false), 75 StandardCharsets.UTF_8); 76 } catch (IllegalArgumentException ex) { 77 Log.w(TAG, "failed to decode email: " + ex); 78 } 79 } else { 80 Log.d(TAG, "Unhandled subject info: " + s); 81 } 82 } 83 return TextUtils.isEmpty(info.commonName) ? null : info; 84 } 85 86 /** 87 * The characters in a subject string will be escaped based on RFC2253. 88 * To restore the original string, this method unescapes escaped 89 * characters. 90 */ unescapeString(String s)91 private static String unescapeString(String s) { 92 final String escapees = ",=+<>#;\"\\"; 93 StringBuilder res = new StringBuilder(); 94 char[] chars = s.toCharArray(); 95 boolean isEscaped = false; 96 for (char c: chars) { 97 if (c == '\\' && !isEscaped) { 98 isEscaped = true; 99 continue; 100 } 101 // An illegal escaped character is founded. 102 if (isEscaped && escapees.indexOf(c) == -1) { 103 Log.d(TAG, "Unable to unescape string: " + s); 104 return null; 105 } 106 res.append(c); 107 isEscaped = false; 108 } 109 // There is a trailing '\' without a escaped character. 110 if (isEscaped) { 111 Log.d(TAG, "Unable to unescape string: " + s); 112 return null; 113 } 114 return res.toString(); 115 } 116 117 @Override toString()118 public String toString() { 119 StringBuilder sb = new StringBuilder(); 120 sb.append("Raw=").append(rawData) 121 .append(", Common Name=").append(commonName) 122 .append(", Organization=").append(organization) 123 .append(", Location=").append(location) 124 .append(", State=").append(state) 125 .append(", Country=").append(country) 126 .append(", Contact=").append(email); 127 return sb.toString(); 128 } 129 } 130