1 /*
   2  * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 
  25 // common infrastructure for SunPKCS11 tests
  26 
  27 import java.io.BufferedReader;
  28 import java.io.ByteArrayOutputStream;
  29 import java.io.File;
  30 import java.io.FileInputStream;
  31 import java.io.IOException;
  32 import java.io.InputStreamReader;
  33 import java.io.StringReader;
  34 import java.security.AlgorithmParameters;
  35 import java.security.InvalidAlgorithmParameterException;
  36 import java.security.KeyPairGenerator;
  37 import java.security.NoSuchProviderException;
  38 import java.security.Provider;
  39 import java.security.ProviderException;
  40 import java.security.Security;
  41 import java.security.spec.ECGenParameterSpec;
  42 import java.security.spec.ECParameterSpec;
  43 import java.util.ArrayList;
  44 import java.util.Arrays;
  45 import java.util.HashMap;
  46 import java.util.Iterator;
  47 import java.util.List;
  48 import java.util.Map;
  49 import java.util.Properties;
  50 import java.util.ServiceLoader;
  51 import java.util.Set;
  52 
  53 public abstract class PKCS11Test {
  54 
  55     private boolean enableSM = false;
  56 
  57     static final Properties props = System.getProperties();
  58 
  59     static final String PKCS11 = "PKCS11";
  60 
  61     // directory of the test source
  62     static final String BASE = System.getProperty("test.src", ".");
  63 
  64     static final char SEP = File.separatorChar;
  65 
  66     private static final String DEFAULT_POLICY =
  67             BASE + SEP + ".." + SEP + "policy";
  68 
  69     // directory corresponding to BASE in the /closed hierarchy
  70     static final String CLOSED_BASE;
  71 
  72     static {
  73         // hack
  74         String absBase = new File(BASE).getAbsolutePath();
  75         int k = absBase.indexOf(SEP + "test" + SEP + "sun" + SEP);
  76         if (k < 0) k = 0;
  77         String p1 = absBase.substring(0, k + 6);
  78         String p2 = absBase.substring(k + 5);
  79         CLOSED_BASE = p1 + "closed" + p2;
  80 
  81         // set it as a system property to make it available in policy file
  82         System.setProperty("closed.base", CLOSED_BASE);
  83     }
  84 
  85     // NSS version info
  86     public static enum ECCState { None, Basic, Extended };
  87     static double nss_version = -1;
  88     static ECCState nss_ecc_status = ECCState.Extended;
  89 
  90     // The NSS library we need to search for in getNSSLibDir()
  91     // Default is "libsoftokn3.so", listed as "softokn3"
  92     // The other is "libnss3.so", listed as "nss3".
  93     static String nss_library = "softokn3";
  94 
  95     // NSS versions of each library.  It is simplier to keep nss_version
  96     // for quick checking for generic testing than many if-else statements.
  97     static double softoken3_version = -1;
  98     static double nss3_version = -1;
  99     static Provider pkcs11;
 100 
 101     // Goes through ServiceLoader instead of Provider.getInstance() since it
 102     // works on all platforms
 103     static {
 104         ServiceLoader sl = ServiceLoader.load(java.security.Provider.class);
 105         Iterator<Provider> iter = sl.iterator();
 106         Provider p = null;
 107         boolean found = false;
 108         while (iter.hasNext()) {
 109             try {
 110                 p = iter.next();
 111                 if (p.getName().equals("SunPKCS11")) {
 112                     found = true;
 113                     break;
 114                 }
 115             } catch (Exception e) {
 116                 // ignore and move on to the next one
 117             }
 118         }
 119         // Nothing found through ServiceLoader; fall back to reflection
 120         if (!found) {
 121             try {
 122                 Class clazz = Class.forName("sun.security.pkcs11.SunPKCS11");
 123                 p = (Provider) clazz.newInstance();
 124             } catch (Exception ex) {
 125                 ex.printStackTrace();
 126             }
 127         }
 128         pkcs11 = p;
 129     }
 130 
 131     /*
 132      * Use Solaris SPARC 11.2 or later to avoid an intermittent failure
 133      * when running SunPKCS11-Solaris (8044554)
 134      */
 135     static boolean isBadSolarisSparc(Provider p) {
 136         if ("SunPKCS11-Solaris".equals(p.getName()) && badSolarisSparc) {
 137             System.out.println("SunPKCS11-Solaris provider requires " +
 138                 "Solaris SPARC 11.2 or later, skipping");
 139             return true;
 140         }
 141         return false;
 142     }
 143 
 144     // Return a SunPKCS11 provider configured with the specified config file
 145     static Provider getSunPKCS11(String config) throws Exception {
 146         if (pkcs11 == null) {
 147             throw new NoSuchProviderException("No PKCS11 provider available");
 148         }
 149         return pkcs11.configure(config);
 150     }
 151 
 152     public abstract void main(Provider p) throws Exception;
 153 
 154     private void premain(Provider p) throws Exception {
 155         // set a security manager and policy before a test case runs,
 156         // and disable them after the test case finished
 157         try {
 158             if (enableSM) {
 159                 System.setSecurityManager(new SecurityManager());
 160             }
 161             long start = System.currentTimeMillis();
 162             System.out.printf(
 163                     "Running test with provider %s (security manager %s) ...%n",
 164                         p.getName(), enableSM ? "enabled" : "disabled");
 165             main(p);
 166             long stop = System.currentTimeMillis();
 167             System.out.println("Completed test with provider " + p.getName() +
 168                 " (" + (stop - start) + " ms).");
 169         } finally {
 170             if (enableSM) {
 171                 System.setSecurityManager(null);
 172             }
 173         }
 174     }
 175 
 176     public static void main(PKCS11Test test) throws Exception {
 177         main(test, null);
 178     }
 179 
 180     public static void main(PKCS11Test test, String[] args) throws Exception {
 181         if (args != null) {
 182             if (args.length > 0) {
 183                 if ("sm".equals(args[0])) {
 184                     test.enableSM = true;
 185                 } else {
 186                     throw new RuntimeException("Unknown Command, use 'sm' as "
 187                             + "first arguemtn to enable security manager");
 188                 }
 189             }
 190             if (test.enableSM) {
 191                 System.setProperty("java.security.policy",
 192                         (args.length > 1) ? BASE + SEP + args[1]
 193                                 : DEFAULT_POLICY);
 194             }
 195         }
 196 
 197         Provider[] oldProviders = Security.getProviders();
 198         try {
 199             System.out.println("Beginning test run " + test.getClass().getName() + "...");
 200             testDefault(test);
 201             testNSS(test);
 202             testDeimos(test);
 203         } finally {
 204             // NOTE: Do not place a 'return' in any finally block
 205             // as it will suppress exceptions and hide test failures.
 206             Provider[] newProviders = Security.getProviders();
 207             boolean found = true;
 208             // Do not restore providers if nothing changed. This is especailly
 209             // useful for ./Provider/Login.sh, where a SecurityManager exists.
 210             if (oldProviders.length == newProviders.length) {
 211                 found = false;
 212                 for (int i = 0; i<oldProviders.length; i++) {
 213                     if (oldProviders[i] != newProviders[i]) {
 214                         found = true;
 215                         break;
 216                     }
 217                 }
 218             }
 219             if (found) {
 220                 for (Provider p: newProviders) {
 221                     Security.removeProvider(p.getName());
 222                 }
 223                 for (Provider p: oldProviders) {
 224                     Security.addProvider(p);
 225                 }
 226             }
 227         }
 228     }
 229 
 230     public static void testDeimos(PKCS11Test test) throws Exception {
 231         if (new File("/opt/SUNWconn/lib/libpkcs11.so").isFile() == false ||
 232             "true".equals(System.getProperty("NO_DEIMOS"))) {
 233             return;
 234         }
 235         String base = getBase();
 236         String p11config = base + SEP + "nss" + SEP + "p11-deimos.txt";
 237         Provider p = getSunPKCS11(p11config);
 238         test.premain(p);
 239     }
 240 
 241     public static void testDefault(PKCS11Test test) throws Exception {
 242         // run test for default configured PKCS11 providers (if any)
 243 
 244         if ("true".equals(System.getProperty("NO_DEFAULT"))) {
 245             return;
 246         }
 247 
 248         Provider[] providers = Security.getProviders();
 249         for (int i = 0; i < providers.length; i++) {
 250             Provider p = providers[i];
 251             if (p.getName().startsWith("SunPKCS11-")) {
 252                 test.premain(p);
 253             }
 254         }
 255     }
 256 
 257     private static String PKCS11_BASE;
 258     static {
 259         try {
 260             PKCS11_BASE = getBase();
 261         } catch (Exception e) {
 262             // ignore
 263         }
 264     }
 265 
 266     private final static String PKCS11_REL_PATH = "sun/security/pkcs11";
 267 
 268     public static String getBase() throws Exception {
 269         if (PKCS11_BASE != null) {
 270             return PKCS11_BASE;
 271         }
 272         File cwd = new File(System.getProperty("test.src", ".")).getCanonicalFile();
 273         while (true) {
 274             File file = new File(cwd, "TEST.ROOT");
 275             if (file.isFile()) {
 276                 break;
 277             }
 278             cwd = cwd.getParentFile();
 279             if (cwd == null) {
 280                 throw new Exception("Test root directory not found");
 281             }
 282         }
 283         PKCS11_BASE = new File(cwd, PKCS11_REL_PATH.replace('/', SEP)).getAbsolutePath();
 284         return PKCS11_BASE;
 285     }
 286 
 287     public static String getNSSLibDir() throws Exception {
 288         return getNSSLibDir(nss_library);
 289     }
 290 
 291     static String getNSSLibDir(String library) throws Exception {
 292         String osName = props.getProperty("os.name");
 293         if (osName.startsWith("Win")) {
 294             osName = "Windows";
 295         } else if (osName.equals("Mac OS X")) {
 296             osName = "MacOSX";
 297         }
 298         String osid = osName + "-"
 299                 + props.getProperty("os.arch") + "-" + props.getProperty("sun.arch.data.model");
 300         String[] nssLibDirs = osMap.get(osid);
 301         if (nssLibDirs == null) {
 302             System.out.println("Unsupported OS, skipping: " + osid);
 303             return null;
 304         }
 305         if (nssLibDirs.length == 0) {
 306             System.out.println("NSS not supported on this platform, skipping test");
 307             return null;
 308         }
 309         String nssLibDir = null;
 310         for (String dir : nssLibDirs) {
 311             if (new File(dir).exists() &&
 312                 new File(dir + System.mapLibraryName(library)).exists()) {
 313                 nssLibDir = dir;
 314                 System.setProperty("pkcs11test.nss.libdir", nssLibDir);
 315                 break;
 316             }
 317         }
 318         return nssLibDir;
 319     }
 320 
 321     static boolean isBadNSSVersion(Provider p) {
 322         if (isNSS(p) && badNSSVersion) {
 323             System.out.println("NSS 3.11 has a DER issue that recent " +
 324                     "version do not.");
 325             return true;
 326         }
 327         return false;
 328     }
 329 
 330     protected static void safeReload(String lib) throws Exception {
 331         try {
 332             System.load(lib);
 333         } catch (UnsatisfiedLinkError e) {
 334             if (e.getMessage().contains("already loaded")) {
 335                 return;
 336             }
 337         }
 338     }
 339 
 340     static boolean loadNSPR(String libdir) throws Exception {
 341         // load NSS softoken dependencies in advance to avoid resolver issues
 342         safeReload(libdir + System.mapLibraryName("nspr4"));
 343         safeReload(libdir + System.mapLibraryName("plc4"));
 344         safeReload(libdir + System.mapLibraryName("plds4"));
 345         safeReload(libdir + System.mapLibraryName("sqlite3"));
 346         safeReload(libdir + System.mapLibraryName("nssutil3"));
 347         return true;
 348     }
 349 
 350     // Check the provider being used is NSS
 351     public static boolean isNSS(Provider p) {
 352         return p.getName().toUpperCase().equals("SUNPKCS11-NSS");
 353     }
 354 
 355     static double getNSSVersion() {
 356         if (nss_version == -1)
 357             getNSSInfo();
 358         return nss_version;
 359     }
 360 
 361     static ECCState getNSSECC() {
 362         if (nss_version == -1)
 363             getNSSInfo();
 364         return nss_ecc_status;
 365     }
 366 
 367     public static double getLibsoftokn3Version() {
 368         if (softoken3_version == -1)
 369             return getNSSInfo("softokn3");
 370         return softoken3_version;
 371     }
 372 
 373     public static double getLibnss3Version() {
 374         if (nss3_version == -1)
 375             return getNSSInfo("nss3");
 376         return nss3_version;
 377     }
 378 
 379     /* Read the library to find out the verison */
 380     static void getNSSInfo() {
 381         getNSSInfo(nss_library);
 382     }
 383 
 384     static double getNSSInfo(String library) {
 385         // look for two types of headers in NSS libraries
 386         String nssHeader1 = "$Header: NSS";
 387         String nssHeader2 = "Version: NSS";
 388         boolean found = false;
 389         String s = null;
 390         int i = 0;
 391         String libfile = "";
 392 
 393         if (library.compareTo("softokn3") == 0 && softoken3_version > -1)
 394             return softoken3_version;
 395         if (library.compareTo("nss3") == 0 && nss3_version > -1)
 396             return nss3_version;
 397 
 398         try {
 399             libfile = getNSSLibDir() + System.mapLibraryName(library);
 400             try (FileInputStream is = new FileInputStream(libfile)) {
 401                 byte[] data = new byte[1000];
 402                 int read = 0;
 403 
 404                 while (is.available() > 0) {
 405                     if (read == 0) {
 406                         read = is.read(data, 0, 1000);
 407                     } else {
 408                         // Prepend last 100 bytes in case the header was split
 409                         // between the reads.
 410                         System.arraycopy(data, 900, data, 0, 100);
 411                         read = 100 + is.read(data, 100, 900);
 412                     }
 413 
 414                     s = new String(data, 0, read);
 415                     i = s.indexOf(nssHeader1);
 416                     if (i > 0 || (i = s.indexOf(nssHeader2)) > 0) {
 417                         found = true;
 418                         // If the nssHeader is before 920 we can break, otherwise
 419                         // we may not have the whole header so do another read.  If
 420                         // no bytes are in the stream, that is ok, found is true.
 421                         if (i < 920) {
 422                             break;
 423                         }
 424                     }
 425                 }
 426             }
 427         } catch (Exception e) {
 428             e.printStackTrace();
 429         }
 430 
 431         if (!found) {
 432             System.out.println("lib" + library +
 433                     " version not found, set to 0.0: " + libfile);
 434             nss_version = 0.0;
 435             return nss_version;
 436         }
 437 
 438         // the index after whitespace after nssHeader
 439         int afterheader = s.indexOf("NSS", i) + 4;
 440         String version = s.substring(afterheader, s.indexOf(' ', afterheader));
 441 
 442         // If a "dot dot" release, strip the extra dots for double parsing
 443         String[] dot = version.split("\\.");
 444         if (dot.length > 2) {
 445             version = dot[0]+"."+dot[1];
 446             for (int j = 2; dot.length > j; j++) {
 447                 version += dot[j];
 448             }
 449         }
 450 
 451         // Convert to double for easier version value checking
 452         try {
 453             nss_version = Double.parseDouble(version);
 454         } catch (NumberFormatException e) {
 455             System.out.println("Failed to parse lib" + library +
 456                     " version. Set to 0.0");
 457             e.printStackTrace();
 458         }
 459 
 460         System.out.print("lib" + library + " version = "+version+".  ");
 461 
 462         // Check for ECC
 463         if (s.indexOf("Basic") > 0) {
 464             nss_ecc_status = ECCState.Basic;
 465             System.out.println("ECC Basic.");
 466         } else if (s.indexOf("Extended") > 0) {
 467             nss_ecc_status = ECCState.Extended;
 468             System.out.println("ECC Extended.");
 469         } else {
 470             System.out.println("ECC None.");
 471         }
 472 
 473         if (library.compareTo("softokn3") == 0) {
 474             softoken3_version = nss_version;
 475         } else if (library.compareTo("nss3") == 0) {
 476             nss3_version = nss_version;
 477         }
 478 
 479         return nss_version;
 480     }
 481 
 482     // Used to set the nss_library file to search for libsoftokn3.so
 483     public static void useNSS() {
 484         nss_library = "nss3";
 485     }
 486 
 487     public static void testNSS(PKCS11Test test) throws Exception {
 488         String libdir = getNSSLibDir();
 489         if (libdir == null) {
 490             return;
 491         }
 492         String base = getBase();
 493 
 494         if (loadNSPR(libdir) == false) {
 495             return;
 496         }
 497 
 498         String libfile = libdir + System.mapLibraryName(nss_library);
 499 
 500         String customDBdir = System.getProperty("CUSTOM_DB_DIR");
 501         String dbdir = (customDBdir != null) ?
 502                                 customDBdir :
 503                                 base + SEP + "nss" + SEP + "db";
 504         // NSS always wants forward slashes for the config path
 505         dbdir = dbdir.replace('\\', '/');
 506 
 507         String customConfig = System.getProperty("CUSTOM_P11_CONFIG");
 508         String customConfigName = System.getProperty("CUSTOM_P11_CONFIG_NAME", "p11-nss.txt");
 509         String p11config = (customConfig != null) ?
 510                                 customConfig :
 511                                 base + SEP + "nss" + SEP + customConfigName;
 512 
 513         System.setProperty("pkcs11test.nss.lib", libfile);
 514         System.setProperty("pkcs11test.nss.db", dbdir);
 515         Provider p = getSunPKCS11(p11config);
 516         test.premain(p);
 517     }
 518 
 519     // Generate a vector of supported elliptic curves of a given provider
 520     static List<ECParameterSpec> getKnownCurves(Provider p) throws Exception {
 521         int index;
 522         int begin;
 523         int end;
 524         String curve;
 525 
 526         List<ECParameterSpec> results = new ArrayList<>();
 527         // Get Curves to test from SunEC.
 528         String kcProp = Security.getProvider("SunEC").
 529                 getProperty("AlgorithmParameters.EC SupportedCurves");
 530 
 531         if (kcProp == null) {
 532             throw new RuntimeException(
 533             "\"AlgorithmParameters.EC SupportedCurves property\" not found");
 534         }
 535 
 536         System.out.println("Finding supported curves using list from SunEC\n");
 537         index = 0;
 538         for (;;) {
 539             // Each set of curve names is enclosed with brackets.
 540             begin = kcProp.indexOf('[', index);
 541             end = kcProp.indexOf(']', index);
 542             if (begin == -1 || end == -1) {
 543                 break;
 544             }
 545 
 546             /*
 547              * Each name is separated by a comma.
 548              * Just get the first name in the set.
 549              */
 550             index = end + 1;
 551             begin++;
 552             end = kcProp.indexOf(',', begin);
 553             if (end == -1) {
 554                 // Only one name in the set.
 555                 end = index -1;
 556             }
 557 
 558             curve = kcProp.substring(begin, end);
 559             ECParameterSpec e = getECParameterSpec(p, curve);
 560             System.out.print("\t "+ curve + ": ");
 561             try {
 562                 KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", p);
 563                 kpg.initialize(e);
 564                 kpg.generateKeyPair();
 565                 results.add(e);
 566                 System.out.println("Supported");
 567             } catch (ProviderException ex) {
 568                 System.out.println("Unsupported: PKCS11: " +
 569                         ex.getCause().getMessage());
 570             } catch (InvalidAlgorithmParameterException ex) {
 571                 System.out.println("Unsupported: Key Length: " +
 572                         ex.getMessage());
 573             }
 574         }
 575 
 576         if (results.size() == 0) {
 577             throw new RuntimeException("No supported EC curves found");
 578         }
 579 
 580         return results;
 581     }
 582 
 583     private static ECParameterSpec getECParameterSpec(Provider p, String name)
 584             throws Exception {
 585 
 586         AlgorithmParameters parameters =
 587             AlgorithmParameters.getInstance("EC", p);
 588 
 589         parameters.init(new ECGenParameterSpec(name));
 590 
 591         return parameters.getParameterSpec(ECParameterSpec.class);
 592     }
 593 
 594     // Check support for a curve with a provided Vector of EC support
 595     boolean checkSupport(List<ECParameterSpec> supportedEC,
 596             ECParameterSpec curve) {
 597         for (ECParameterSpec ec: supportedEC) {
 598             if (ec.equals(curve)) {
 599                 return true;
 600             }
 601         }
 602         return false;
 603     }
 604 
 605     private static final Map<String,String[]> osMap;
 606 
 607     // Location of the NSS libraries on each supported platform
 608     static {
 609         osMap = new HashMap<>();
 610         osMap.put("SunOS-sparc-32", new String[]{"/usr/lib/mps/"});
 611         osMap.put("SunOS-sparcv9-64", new String[]{"/usr/lib/mps/64/"});
 612         osMap.put("SunOS-x86-32", new String[]{"/usr/lib/mps/"});
 613         osMap.put("SunOS-amd64-64", new String[]{"/usr/lib/mps/64/"});
 614         osMap.put("Linux-i386-32", new String[]{
 615             "/usr/lib/i386-linux-gnu/", "/usr/lib32/", "/usr/lib/"});
 616         osMap.put("Linux-amd64-64", new String[]{
 617             "/usr/lib/x86_64-linux-gnu/", "/usr/lib/x86_64-linux-gnu/nss/",
 618             "/usr/lib64/"});
 619         osMap.put("Linux-ppc64-64", new String[]{"/usr/lib64/"});
 620         osMap.put("Linux-ppc64le-64", new String[]{"/usr/lib64/"});
 621         osMap.put("Windows-x86-32", new String[]{
 622             PKCS11_BASE + "/nss/lib/windows-i586/".replace('/', SEP)});
 623         osMap.put("Windows-amd64-64", new String[]{
 624             PKCS11_BASE + "/nss/lib/windows-amd64/".replace('/', SEP)});
 625         osMap.put("MacOSX-x86_64-64", new String[]{
 626             PKCS11_BASE + "/nss/lib/macosx-x86_64/"});
 627     }
 628 
 629     private final static char[] hexDigits = "0123456789abcdef".toCharArray();
 630 
 631     static final boolean badNSSVersion =
 632             getNSSVersion() >= 3.11 && getNSSVersion() < 3.12;
 633 
 634     static final boolean badSolarisSparc =
 635             System.getProperty("os.name").equals("SunOS") &&
 636             System.getProperty("os.arch").equals("sparcv9") &&
 637             System.getProperty("os.version").compareTo("5.11") <= 0 &&
 638             getDistro().compareTo("11.2") < 0;
 639 
 640     public static String toString(byte[] b) {
 641         if (b == null) {
 642             return "(null)";
 643         }
 644         StringBuilder sb = new StringBuilder(b.length * 3);
 645         for (int i = 0; i < b.length; i++) {
 646             int k = b[i] & 0xff;
 647             if (i != 0) {
 648                 sb.append(':');
 649             }
 650             sb.append(hexDigits[k >>> 4]);
 651             sb.append(hexDigits[k & 0xf]);
 652         }
 653         return sb.toString();
 654     }
 655 
 656     public static byte[] parse(String s) {
 657         if (s.equals("(null)")) {
 658             return null;
 659         }
 660         try {
 661             int n = s.length();
 662             ByteArrayOutputStream out = new ByteArrayOutputStream(n / 3);
 663             StringReader r = new StringReader(s);
 664             while (true) {
 665                 int b1 = nextNibble(r);
 666                 if (b1 < 0) {
 667                     break;
 668                 }
 669                 int b2 = nextNibble(r);
 670                 if (b2 < 0) {
 671                     throw new RuntimeException("Invalid string " + s);
 672                 }
 673                 int b = (b1 << 4) | b2;
 674                 out.write(b);
 675             }
 676             return out.toByteArray();
 677         } catch (IOException e) {
 678             throw new RuntimeException(e);
 679         }
 680     }
 681 
 682     private static int nextNibble(StringReader r) throws IOException {
 683         while (true) {
 684             int ch = r.read();
 685             if (ch == -1) {
 686                 return -1;
 687             } else if ((ch >= '0') && (ch <= '9')) {
 688                 return ch - '0';
 689             } else if ((ch >= 'a') && (ch <= 'f')) {
 690                 return ch - 'a' + 10;
 691             } else if ((ch >= 'A') && (ch <= 'F')) {
 692                 return ch - 'A' + 10;
 693             }
 694         }
 695     }
 696 
 697     <T> T[] concat(T[] a, T[] b) {
 698         if ((b == null) || (b.length == 0)) {
 699             return a;
 700         }
 701         T[] r = Arrays.copyOf(a, a.length + b.length);
 702         System.arraycopy(b, 0, r, a.length, b.length);
 703         return r;
 704     }
 705 
 706     /**
 707      * Returns supported algorithms of specified type.
 708      */
 709     static List<String> getSupportedAlgorithms(String type, String alg,
 710             Provider p) {
 711         // prepare a list of supported algorithms
 712         List<String> algorithms = new ArrayList<>();
 713         Set<Provider.Service> services = p.getServices();
 714         for (Provider.Service service : services) {
 715             if (service.getType().equals(type)
 716                     && service.getAlgorithm().startsWith(alg)) {
 717                 algorithms.add(service.getAlgorithm());
 718             }
 719         }
 720         return algorithms;
 721     }
 722 
 723     /**
 724      * Get the identifier for the operating system distribution
 725      */
 726     static String getDistro() {
 727         try (BufferedReader in =
 728             new BufferedReader(new InputStreamReader(
 729                 Runtime.getRuntime().exec("uname -v").getInputStream()))) {
 730 
 731             return in.readLine();
 732         } catch (Exception e) {
 733             return "";
 734         }
 735     }
 736 
 737     static byte[] generateData(int length) {
 738         byte data[] = new byte[length];
 739         for (int i=0; i<data.length; i++) {
 740             data[i] = (byte) (i % 256);
 741         }
 742         return data;
 743     }
 744 }