Home | History | Annotate | Download | only in osu
      1 package com.android.hotspot2.osu;
      2 
      3 /*
      4  * policy-server.r2-testbed             IN      A       10.123.107.107
      5  * remediation-server.r2-testbed        IN      A       10.123.107.107
      6  * subscription-server.r2-testbed       IN      A       10.123.107.107
      7  * www.r2-testbed                       IN      A       10.123.107.107
      8  * osu-server.r2-testbed-rks            IN      A       10.123.107.107
      9  * policy-server.r2-testbed-rks         IN      A       10.123.107.107
     10  * remediation-server.r2-testbed-rks    IN      A       10.123.107.107
     11  * subscription-server.r2-testbed-rks   IN      A       10.123.107.107
     12  */
     13 
     14 import android.content.Context;
     15 import android.content.Intent;
     16 import android.net.Network;
     17 import android.util.Log;
     18 
     19 import com.android.hotspot2.OMADMAdapter;
     20 import com.android.hotspot2.est.ESTHandler;
     21 import com.android.hotspot2.flow.OSUInfo;
     22 import com.android.hotspot2.flow.PlatformAdapter;
     23 import com.android.hotspot2.omadm.OMAConstants;
     24 import com.android.hotspot2.omadm.OMANode;
     25 import com.android.hotspot2.osu.commands.BrowserURI;
     26 import com.android.hotspot2.osu.commands.ClientCertInfo;
     27 import com.android.hotspot2.osu.commands.GetCertData;
     28 import com.android.hotspot2.osu.commands.MOData;
     29 import com.android.hotspot2.osu.service.RedirectListener;
     30 import com.android.hotspot2.pps.Credential;
     31 import com.android.hotspot2.pps.HomeSP;
     32 import com.android.hotspot2.pps.UpdateInfo;
     33 
     34 import java.io.IOException;
     35 import java.net.MalformedURLException;
     36 import java.net.URL;
     37 import java.nio.charset.StandardCharsets;
     38 import java.security.GeneralSecurityException;
     39 import java.security.KeyStore;
     40 import java.security.PrivateKey;
     41 import java.security.cert.CertificateFactory;
     42 import java.security.cert.X509Certificate;
     43 import java.util.ArrayList;
     44 import java.util.Arrays;
     45 import java.util.Collection;
     46 import java.util.Collections;
     47 import java.util.HashMap;
     48 import java.util.Iterator;
     49 import java.util.List;
     50 import java.util.Locale;
     51 import java.util.Map;
     52 
     53 import javax.net.ssl.KeyManager;
     54 
     55 public class OSUClient {
     56     private static final String TAG = "OSUCLT";
     57 
     58     private final OSUInfo mOSUInfo;
     59     private final URL mURL;
     60     private final KeyStore mKeyStore;
     61     private final Context mContext;
     62     private volatile HTTPHandler mHTTPHandler;
     63     private volatile RedirectListener mRedirectListener;
     64 
     65     public OSUClient(OSUInfo osuInfo, KeyStore ks, Context context) throws MalformedURLException {
     66         mOSUInfo = osuInfo;
     67         mURL = new URL(osuInfo.getOSUProvider().getOSUServer());
     68         mKeyStore = ks;
     69         mContext = context;
     70     }
     71 
     72     public OSUClient(String osu, KeyStore ks, Context context) throws MalformedURLException {
     73         mOSUInfo = null;
     74         mURL = new URL(osu);
     75         mKeyStore = ks;
     76         mContext = context;
     77     }
     78 
     79     public OSUInfo getOSUInfo() {
     80         return mOSUInfo;
     81     }
     82 
     83     public void provision(PlatformAdapter platformAdapter, Network network, KeyManager km)
     84             throws IOException, GeneralSecurityException {
     85         try (HTTPHandler httpHandler = new HTTPHandler(StandardCharsets.UTF_8,
     86                 OSUSocketFactory.getSocketFactory(mKeyStore, null,
     87                         OSUFlowManager.FlowType.Provisioning, network, mURL, km, true))) {
     88 
     89             mHTTPHandler = httpHandler;
     90 
     91             SPVerifier spVerifier = new SPVerifier(mOSUInfo);
     92             spVerifier.verify(httpHandler.getOSUCertificate(mURL));
     93 
     94             URL redirectURL = prepareUserInput(platformAdapter,
     95                     mOSUInfo.getName(Locale.getDefault()));
     96             OMADMAdapter omadmAdapter = getOMADMAdapter();
     97 
     98             String regRequest = SOAPBuilder.buildPostDevDataResponse(RequestReason.SubRegistration,
     99                     null,
    100                     redirectURL.toString(),
    101                     omadmAdapter.getMO(OMAConstants.DevInfoURN),
    102                     omadmAdapter.getMO(OMAConstants.DevDetailURN));
    103             Log.d(TAG, "Registration request: " + regRequest);
    104             OSUResponse osuResponse = httpHandler.exchangeSOAP(mURL, regRequest);
    105 
    106             Log.d(TAG, "Response: " + osuResponse);
    107             if (osuResponse.getMessageType() != OSUMessageType.PostDevData) {
    108                 throw new IOException("Expected a PostDevDataResponse");
    109             }
    110             PostDevDataResponse regResponse = (PostDevDataResponse) osuResponse;
    111             String sessionID = regResponse.getSessionID();
    112             if (regResponse.getExecCommand() == ExecCommand.UseClientCertTLS) {
    113                 ClientCertInfo ccInfo = (ClientCertInfo) regResponse.getCommandData();
    114                 if (ccInfo.doesAcceptMfgCerts()) {
    115                     throw new IOException("Mfg certs are not supported in Android");
    116                 } else if (ccInfo.doesAcceptProviderCerts()) {
    117                     ((WiFiKeyManager) km).enableClientAuth(ccInfo.getIssuerNames());
    118                     httpHandler.renegotiate(null, null);
    119                 } else {
    120                     throw new IOException("Neither manufacturer nor provider cert specified");
    121                 }
    122                 regRequest = SOAPBuilder.buildPostDevDataResponse(RequestReason.SubRegistration,
    123                         sessionID,
    124                         redirectURL.toString(),
    125                         omadmAdapter.getMO(OMAConstants.DevInfoURN),
    126                         omadmAdapter.getMO(OMAConstants.DevDetailURN));
    127 
    128                 osuResponse = httpHandler.exchangeSOAP(mURL, regRequest);
    129                 if (osuResponse.getMessageType() != OSUMessageType.PostDevData) {
    130                     throw new IOException("Expected a PostDevDataResponse");
    131                 }
    132                 regResponse = (PostDevDataResponse) osuResponse;
    133             }
    134 
    135             if (regResponse.getExecCommand() != ExecCommand.Browser) {
    136                 throw new IOException("Expected a launchBrowser command");
    137             }
    138             Log.d(TAG, "Exec: " + regResponse.getExecCommand() + ", for '" +
    139                     regResponse.getCommandData() + "'");
    140 
    141             if (!osuResponse.getSessionID().equals(sessionID)) {
    142                 throw new IOException("Mismatching session IDs");
    143             }
    144             String webURL = ((BrowserURI) regResponse.getCommandData()).getURI();
    145 
    146             if (webURL == null) {
    147                 throw new IOException("No web-url");
    148             } else if (!webURL.contains(sessionID)) {
    149                 throw new IOException("Bad or missing session ID in webURL");
    150             }
    151 
    152             if (!startUserInput(new URL(webURL), network)) {
    153                 throw new IOException("User session failed");
    154             }
    155 
    156             Log.d(TAG, " -- Sending user input complete:");
    157             String userComplete = SOAPBuilder.buildPostDevDataResponse(RequestReason.InputComplete,
    158                     sessionID, null,
    159                     omadmAdapter.getMO(OMAConstants.DevInfoURN),
    160                     omadmAdapter.getMO(OMAConstants.DevDetailURN));
    161             OSUResponse moResponse1 = httpHandler.exchangeSOAP(mURL, userComplete);
    162             if (moResponse1.getMessageType() != OSUMessageType.PostDevData) {
    163                 throw new IOException("Bad user input complete response: " + moResponse1);
    164             }
    165             PostDevDataResponse provResponse = (PostDevDataResponse) moResponse1;
    166             GetCertData estData = checkResponse(provResponse);
    167 
    168             Map<OSUCertType, List<X509Certificate>> certs = new HashMap<>();
    169             PrivateKey clientKey = null;
    170 
    171             MOData moData;
    172             if (estData == null) {
    173                 moData = (MOData) provResponse.getCommandData();
    174             } else {
    175                 try (ESTHandler estHandler = new ESTHandler((GetCertData) provResponse.
    176                         getCommandData(), network, getOMADMAdapter(),
    177                         km, mKeyStore, null, OSUFlowManager.FlowType.Provisioning)) {
    178                     estHandler.execute(false);
    179                     certs.put(OSUCertType.CA, estHandler.getCACerts());
    180                     certs.put(OSUCertType.Client, estHandler.getClientCerts());
    181                     clientKey = estHandler.getClientKey();
    182                 }
    183 
    184                 Log.d(TAG, " -- Sending provisioning cert enrollment complete:");
    185                 String certComplete =
    186                         SOAPBuilder.buildPostDevDataResponse(RequestReason.CertEnrollmentComplete,
    187                                 sessionID, null,
    188                                 omadmAdapter.getMO(OMAConstants.DevInfoURN),
    189                                 omadmAdapter.getMO(OMAConstants.DevDetailURN));
    190                 OSUResponse moResponse2 = httpHandler.exchangeSOAP(mURL, certComplete);
    191                 if (moResponse2.getMessageType() != OSUMessageType.PostDevData) {
    192                     throw new IOException("Bad cert enrollment complete response: " + moResponse2);
    193                 }
    194                 PostDevDataResponse provComplete = (PostDevDataResponse) moResponse2;
    195                 if (provComplete.getStatus() != OSUStatus.ProvComplete ||
    196                         provComplete.getOSUCommand() != OSUCommandID.AddMO) {
    197                     throw new IOException("Expected addMO: " + provComplete);
    198                 }
    199                 moData = (MOData) provComplete.getCommandData();
    200             }
    201 
    202             // !!! How can an ExchangeComplete be sent w/o knowing the fate of the certs???
    203             String updateResponse = SOAPBuilder.buildUpdateResponse(sessionID, null);
    204             Log.d(TAG, " -- Sending updateResponse:");
    205             OSUResponse exComplete = httpHandler.exchangeSOAP(mURL, updateResponse);
    206             Log.d(TAG, "exComplete response: " + exComplete);
    207             if (exComplete.getMessageType() != OSUMessageType.ExchangeComplete) {
    208                 throw new IOException("Expected ExchangeComplete: " + exComplete);
    209             } else if (exComplete.getStatus() != OSUStatus.ExchangeComplete) {
    210                 throw new IOException("Bad ExchangeComplete status: " + exComplete);
    211             }
    212 
    213             retrieveCerts(moData.getMOTree().getRoot(), certs, network, km, mKeyStore);
    214             platformAdapter.provisioningComplete(mOSUInfo, moData, certs, clientKey, network);
    215         }
    216     }
    217 
    218     public void remediate(PlatformAdapter platformAdapter, Network network, KeyManager km,
    219             HomeSP homeSP, OSUFlowManager.FlowType flowType)
    220             throws IOException, GeneralSecurityException {
    221         try (HTTPHandler httpHandler = createHandler(network, homeSP, km, flowType)) {
    222 
    223             mHTTPHandler = httpHandler;
    224 
    225             URL redirectURL = prepareUserInput(platformAdapter, homeSP.getFriendlyName());
    226             OMADMAdapter omadmAdapter = getOMADMAdapter();
    227 
    228             String regRequest = SOAPBuilder.buildPostDevDataResponse(RequestReason.SubRemediation,
    229                     null,
    230                     redirectURL.toString(),
    231                     omadmAdapter.getMO(OMAConstants.DevInfoURN),
    232                     omadmAdapter.getMO(OMAConstants.DevDetailURN));
    233 
    234             OSUResponse serverResponse = httpHandler.exchangeSOAP(mURL, regRequest);
    235             if (serverResponse.getMessageType() != OSUMessageType.PostDevData) {
    236                 throw new IOException("Expected a PostDevDataResponse");
    237             }
    238             String sessionID = serverResponse.getSessionID();
    239 
    240             PostDevDataResponse pddResponse = (PostDevDataResponse) serverResponse;
    241             Log.d(TAG, "Remediation response: " + pddResponse);
    242 
    243             Map<OSUCertType, List<X509Certificate>> certs = null;
    244             PrivateKey clientKey = null;
    245 
    246             if (pddResponse.getStatus() != OSUStatus.RemediationComplete) {
    247                 if (pddResponse.getExecCommand() == ExecCommand.UploadMO) {
    248                     String ulMessage = SOAPBuilder.buildPostDevDataResponse(RequestReason.MOUpload,
    249                             null,
    250                             redirectURL.toString(),
    251                             omadmAdapter.getMO(OMAConstants.DevInfoURN),
    252                             omadmAdapter.getMO(OMAConstants.DevDetailURN),
    253                             platformAdapter.getMOTree(homeSP));
    254 
    255                     Log.d(TAG, "Upload MO: " + ulMessage);
    256 
    257                     OSUResponse ulResponse = httpHandler.exchangeSOAP(mURL, ulMessage);
    258                     if (ulResponse.getMessageType() != OSUMessageType.PostDevData) {
    259                         throw new IOException("Expected a PostDevDataResponse to MOUpload");
    260                     }
    261                     pddResponse = (PostDevDataResponse) ulResponse;
    262                 }
    263 
    264                 if (pddResponse.getExecCommand() == ExecCommand.Browser) {
    265                     if (flowType == OSUFlowManager.FlowType.Policy) {
    266                         throw new IOException("Browser launch requested in policy flow");
    267                     }
    268                     String webURL = ((BrowserURI) pddResponse.getCommandData()).getURI();
    269 
    270                     if (webURL == null) {
    271                         throw new IOException("No web-url");
    272                     } else if (!webURL.contains(sessionID)) {
    273                         throw new IOException("Bad or missing session ID in webURL");
    274                     }
    275 
    276                     if (!startUserInput(new URL(webURL), network)) {
    277                         throw new IOException("User session failed");
    278                     }
    279 
    280                     Log.d(TAG, " -- Sending user input complete:");
    281                     String userComplete =
    282                             SOAPBuilder.buildPostDevDataResponse(RequestReason.InputComplete,
    283                                     sessionID, null,
    284                                     omadmAdapter.getMO(OMAConstants.DevInfoURN),
    285                                     omadmAdapter.getMO(OMAConstants.DevDetailURN));
    286 
    287                     OSUResponse udResponse = httpHandler.exchangeSOAP(mURL, userComplete);
    288                     if (udResponse.getMessageType() != OSUMessageType.PostDevData) {
    289                         throw new IOException("Bad user input complete response: " + udResponse);
    290                     }
    291                     pddResponse = (PostDevDataResponse) udResponse;
    292                 } else if (pddResponse.getExecCommand() == ExecCommand.GetCert) {
    293                     certs = new HashMap<>();
    294                     try (ESTHandler estHandler = new ESTHandler((GetCertData) pddResponse.
    295                             getCommandData(), network, getOMADMAdapter(),
    296                             km, mKeyStore, homeSP, flowType)) {
    297                         estHandler.execute(true);
    298                         certs.put(OSUCertType.CA, estHandler.getCACerts());
    299                         certs.put(OSUCertType.Client, estHandler.getClientCerts());
    300                         clientKey = estHandler.getClientKey();
    301                     }
    302 
    303                     if (httpHandler.isHTTPAuthPerformed()) {        // 8.4.3.6
    304                         httpHandler.renegotiate(certs, clientKey);
    305                     }
    306 
    307                     Log.d(TAG, " -- Sending remediation cert enrollment complete:");
    308                     // 8.4.3.5 in the spec actually prescribes that an update URI is sent here,
    309                     // but there is no remediation flow that defines user interaction after EST
    310                     // so for now a null is passed.
    311                     String certComplete =
    312                             SOAPBuilder
    313                                     .buildPostDevDataResponse(RequestReason.CertEnrollmentComplete,
    314                                             sessionID, null,
    315                                             omadmAdapter.getMO(OMAConstants.DevInfoURN),
    316                                             omadmAdapter.getMO(OMAConstants.DevDetailURN));
    317                     OSUResponse ceResponse = httpHandler.exchangeSOAP(mURL, certComplete);
    318                     if (ceResponse.getMessageType() != OSUMessageType.PostDevData) {
    319                         throw new IOException("Bad cert enrollment complete response: "
    320                                 + ceResponse);
    321                     }
    322                     pddResponse = (PostDevDataResponse) ceResponse;
    323                 } else {
    324                     throw new IOException("Unexpected command: " + pddResponse.getExecCommand());
    325                 }
    326             }
    327 
    328             if (pddResponse.getStatus() != OSUStatus.RemediationComplete) {
    329                 throw new IOException("Expected a PostDevDataResponse to MOUpload");
    330             }
    331 
    332             Log.d(TAG, "Remediation response: " + pddResponse);
    333 
    334             List<MOData> mods = new ArrayList<>();
    335             for (OSUCommand command : pddResponse.getCommands()) {
    336                 if (command.getOSUCommand() == OSUCommandID.UpdateNode) {
    337                     mods.add((MOData) command.getCommandData());
    338                 } else if (command.getOSUCommand() != OSUCommandID.NoMOUpdate) {
    339                     throw new IOException("Unexpected OSU response: " + command);
    340                 }
    341             }
    342 
    343             // 1. Machine remediation: Remediation complete + replace node
    344             // 2a. User remediation with upload: ExecCommand.UploadMO
    345             // 2b. User remediation without upload: ExecCommand.Browser
    346             // 3. User remediation only: -> sppPostDevData user input complete
    347             //
    348             // 4. Update node
    349             // 5. -> Update response
    350             // 6. Exchange complete
    351 
    352             OSUError error = null;
    353 
    354             String updateResponse = SOAPBuilder.buildUpdateResponse(sessionID, error);
    355             Log.d(TAG, " -- Sending updateResponse:");
    356             OSUResponse exComplete = httpHandler.exchangeSOAP(mURL, updateResponse);
    357             Log.d(TAG, "exComplete response: " + exComplete);
    358             if (exComplete.getMessageType() != OSUMessageType.ExchangeComplete) {
    359                 throw new IOException("Expected ExchangeComplete: " + exComplete);
    360             } else if (exComplete.getStatus() != OSUStatus.ExchangeComplete) {
    361                 throw new IOException("Bad ExchangeComplete status: " + exComplete);
    362             }
    363 
    364             // There's a chicken and egg here: If the config is saved before sending update complete
    365             // the network is lost and the remediation flow fails.
    366             try {
    367                 platformAdapter.remediationComplete(homeSP, mods, certs, clientKey,
    368                         flowType == OSUFlowManager.FlowType.Policy);
    369             } catch (IOException | GeneralSecurityException e) {
    370                 platformAdapter.provisioningFailed(homeSP.getFriendlyName(), e.getMessage());
    371                 error = OSUError.CommandFailed;
    372             }
    373         }
    374     }
    375 
    376     private OMADMAdapter getOMADMAdapter() {
    377         return OMADMAdapter.getInstance(mContext);
    378     }
    379 
    380     private URL prepareUserInput(PlatformAdapter platformAdapter, String spName)
    381             throws IOException {
    382         mRedirectListener = new RedirectListener(platformAdapter, spName);
    383         return mRedirectListener.getURL();
    384     }
    385 
    386     private boolean startUserInput(URL target, Network network)
    387             throws IOException {
    388         mRedirectListener.startService();
    389 
    390         Intent intent = new Intent(mContext, OSUWebView.class);
    391         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    392         intent.putExtra(OSUWebView.OSU_NETWORK, network);
    393         intent.putExtra(OSUWebView.OSU_URL, target.toString());
    394         mContext.startActivity(intent);
    395 
    396         return mRedirectListener.waitForUser();
    397     }
    398 
    399     public void close(boolean abort) {
    400         if (mRedirectListener != null) {
    401             mRedirectListener.abort();
    402             mRedirectListener = null;
    403         }
    404         if (abort) {
    405             try {
    406                 mHTTPHandler.close();
    407             } catch (IOException ioe) {
    408                 /**/
    409             }
    410         }
    411     }
    412 
    413     private HTTPHandler createHandler(Network network, HomeSP homeSP, KeyManager km,
    414             OSUFlowManager.FlowType flowType)
    415             throws GeneralSecurityException, IOException {
    416         Credential credential = homeSP.getCredential();
    417 
    418         Log.d(TAG, "Credential method " + credential.getEAPMethod().getEAPMethodID());
    419         switch (credential.getEAPMethod().getEAPMethodID()) {
    420             case EAP_TTLS:
    421                 String user;
    422                 byte[] password;
    423                 UpdateInfo subscriptionUpdate;
    424                 if (flowType == OSUFlowManager.FlowType.Policy) {
    425                     subscriptionUpdate = homeSP.getPolicy() != null ?
    426                             homeSP.getPolicy().getPolicyUpdate() : null;
    427                 } else {
    428                     subscriptionUpdate = homeSP.getSubscriptionUpdate();
    429                 }
    430                 if (subscriptionUpdate != null && subscriptionUpdate.getUsername() != null) {
    431                     user = subscriptionUpdate.getUsername();
    432                     password = subscriptionUpdate.getPassword() != null ?
    433                             subscriptionUpdate.getPassword().getBytes(StandardCharsets.UTF_8) :
    434                             new byte[0];
    435                 } else {
    436                     user = credential.getUserName();
    437                     password = credential.getPassword().getBytes(StandardCharsets.UTF_8);
    438                 }
    439                 return new HTTPHandler(StandardCharsets.UTF_8,
    440                         OSUSocketFactory.getSocketFactory(mKeyStore, homeSP, flowType, network,
    441                                 mURL, km, true), user, password);
    442             case EAP_TLS:
    443                 return new HTTPHandler(StandardCharsets.UTF_8,
    444                         OSUSocketFactory.getSocketFactory(mKeyStore, homeSP, flowType, network,
    445                                 mURL, km, true));
    446             default:
    447                 throw new IOException("Cannot remediate account with " +
    448                         credential.getEAPMethod().getEAPMethodID());
    449         }
    450     }
    451 
    452     private static GetCertData checkResponse(PostDevDataResponse response) throws IOException {
    453         if (response.getStatus() == OSUStatus.ProvComplete &&
    454                 response.getOSUCommand() == OSUCommandID.AddMO) {
    455             return null;
    456         }
    457 
    458         if (response.getOSUCommand() == OSUCommandID.Exec &&
    459                 response.getExecCommand() == ExecCommand.GetCert) {
    460             return (GetCertData) response.getCommandData();
    461         } else {
    462             throw new IOException("Unexpected command: " + response);
    463         }
    464     }
    465 
    466     private static final String[] AAACertPath =
    467             {"PerProviderSubscription", "?", "AAAServerTrustRoot", "*", "CertURL"};
    468     private static final String[] RemdCertPath =
    469             {"PerProviderSubscription", "?", "SubscriptionUpdate", "TrustRoot", "CertURL"};
    470     private static final String[] PolicyCertPath =
    471             {"PerProviderSubscription", "?", "Policy", "PolicyUpdate", "TrustRoot", "CertURL"};
    472 
    473     private static void retrieveCerts(OMANode ppsRoot,
    474                                       Map<OSUCertType, List<X509Certificate>> certs,
    475                                       Network network, KeyManager km, KeyStore ks)
    476             throws GeneralSecurityException, IOException {
    477 
    478         List<X509Certificate> aaaCerts = getCerts(ppsRoot, AAACertPath, network, km, ks);
    479         certs.put(OSUCertType.AAA, aaaCerts);
    480         certs.put(OSUCertType.Remediation, getCerts(ppsRoot, RemdCertPath, network, km, ks));
    481         certs.put(OSUCertType.Policy, getCerts(ppsRoot, PolicyCertPath, network, km, ks));
    482     }
    483 
    484     private static List<X509Certificate> getCerts(OMANode ppsRoot, String[] path, Network network,
    485                                                   KeyManager km, KeyStore ks)
    486             throws GeneralSecurityException, IOException {
    487         List<String> urls = new ArrayList<>();
    488         getCertURLs(ppsRoot, Arrays.asList(path).iterator(), urls);
    489         Log.d(TAG, Arrays.toString(path) + ": " + urls);
    490 
    491         List<X509Certificate> certs = new ArrayList<>(urls.size());
    492         CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
    493         for (String urlString : urls) {
    494             URL url = new URL(urlString);
    495             HTTPHandler httpHandler = new HTTPHandler(StandardCharsets.UTF_8,
    496                     OSUSocketFactory.getSocketFactory(ks, null,
    497                             OSUFlowManager.FlowType.Provisioning, network, url, km, false));
    498 
    499             certs.add((X509Certificate) certFactory.generateCertificate(httpHandler.doGet(url)));
    500         }
    501         return certs;
    502     }
    503 
    504     private static void getCertURLs(OMANode root, Iterator<String> path, List<String> urls)
    505             throws IOException {
    506 
    507         String name = path.next();
    508         // Log.d(TAG, "Pulling '" + name + "' out of '" + root.getName() + "'");
    509         Collection<OMANode> nodes = null;
    510         switch (name) {
    511             case "?":
    512                 for (OMANode node : root.getChildren()) {
    513                     if (!node.isLeaf()) {
    514                         nodes = Collections.singletonList(node);
    515                         break;
    516                     }
    517                 }
    518                 break;
    519             case "*":
    520                 nodes = root.getChildren();
    521                 break;
    522             default:
    523                 nodes = Collections.singletonList(root.getChild(name));
    524                 break;
    525         }
    526 
    527         if (nodes == null) {
    528             throw new IllegalArgumentException("No matching node in " + root.getName()
    529                     + " for " + name);
    530         }
    531 
    532         for (OMANode node : nodes) {
    533             if (path.hasNext()) {
    534                 getCertURLs(node, path, urls);
    535             } else {
    536                 urls.add(node.getValue());
    537             }
    538         }
    539     }
    540 }
    541