• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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