1 /*
   2  * Copyright (c) 2003, 2015, 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.*;
  28 import java.util.*;
  29 
  30 import java.security.*;
  31 import java.security.spec.ECGenParameterSpec;
  32 import java.security.spec.ECParameterSpec;
  33 
  34 public abstract class PKCS11Test {
  35 
  36     // directory of the test source
  37     static final String BASE = System.getProperty("test.src", ".");
  38 
  39     static final char SEP = File.separatorChar;
  40 
  41     private final static String REL_CLOSED = "../../../../closed/sun/security/pkcs11".replace('/', SEP);
  42 
  43     // directory corresponding to BASE in the /closed hierarchy
  44     static final String CLOSED_BASE;
  45 
  46     static {
  47         // hack
  48         String absBase = new File(BASE).getAbsolutePath();
  49         int k = absBase.indexOf(SEP + "test" + SEP + "sun" + SEP);
  50         if (k < 0) k = 0;
  51         String p1 = absBase.substring(0, k + 6);
  52         String p2 = absBase.substring(k + 5);
  53         CLOSED_BASE = p1 + "closed" + p2;
  54     }
  55 
  56     static String NSPR_PREFIX = "";
  57 
  58     // NSS version info
  59     public static enum ECCState { None, Basic, Extended };
  60     static double nss_version = -1;
  61     static ECCState nss_ecc_status = ECCState.Extended;
  62 
  63     // The NSS library we need to search for in getNSSLibDir()
  64     // Default is "libsoftokn3.so", listed as "softokn3"
  65     // The other is "libnss3.so", listed as "nss3".
  66     static String nss_library = "softokn3";
  67 
  68     // NSS versions of each library.  It is simplier to keep nss_version
  69     // for quick checking for generic testing than many if-else statements.
  70     static double softoken3_version = -1;
  71     static double nss3_version = -1;
  72     static Provider sunpkcs11;
  73 
  74     // Goes through ServiceLoader instead Provider.getInstance() since
  75     // SunPKCS11 provider is NOT registered for non-Solaris platforms
  76     static {
  77         ServiceLoader sl = ServiceLoader.load(java.security.Provider.class);
  78         Iterator<Provider> iter = sl.iterator();
  79         Provider p = null;
  80         while (iter.hasNext()) {
  81             try {
  82                 p = iter.next();
  83                 if (p.getName().startsWith("SunPKCS11")) {
  84                     break;
  85                 };
  86             } catch (Exception e) {
  87                 // ignore and move on to the next one
  88             }
  89         }
  90         sunpkcs11 = p;
  91     }
  92 
  93     // Return a SunPKCS11 provider with default config file (Solaris)
  94     // or a dummy one which requires further configuration (non-Solaris)
  95     static Provider getSunPKCS11() throws NoSuchProviderException {
  96         if (sunpkcs11 == null) {
  97             throw new NoSuchProviderException("No PKCS11 provider available");
  98         }
  99         return sunpkcs11;
 100     }
 101 
 102      
 103     // Return a SunPKCS11 provider using the specified config file
 104     static Provider getSunPKCS11(String config) throws Exception {
 105         return getSunPKCS11().configure(config);
 106     }
 107 
 108     public abstract void main(Provider p) throws Exception;
 109 
 110     private void premain(Provider p) throws Exception {
 111         long start = System.currentTimeMillis();
 112         System.out.println("Running test with provider " + p.getName() + "...");
 113         main(p);
 114         long stop = System.currentTimeMillis();
 115         System.out.println("Completed test with provider " + p.getName() +
 116             " (" + (stop - start) + " ms).");
 117     }
 118 
 119     public static void main(PKCS11Test test) throws Exception {
 120         Provider[] oldProviders = Security.getProviders();
 121         try {
 122             System.out.println("Beginning test run " + test.getClass().getName() + "...");
 123             testDefault(test);
 124             testNSS(test);
 125             testDeimos(test);
 126         } finally {
 127             // NOTE: Do not place a 'return' in any finally block
 128             // as it will suppress exceptions and hide test failures.
 129             Provider[] newProviders = Security.getProviders();
 130             boolean found = true;
 131             // Do not restore providers if nothing changed. This is especailly
 132             // useful for ./Provider/Login.sh, where a SecurityManager exists.
 133             if (oldProviders.length == newProviders.length) {
 134                 found = false;
 135                 for (int i = 0; i<oldProviders.length; i++) {
 136                     if (oldProviders[i] != newProviders[i]) {
 137                         found = true;
 138                         break;
 139                     }
 140                 }
 141             }
 142             if (found) {
 143                 for (Provider p: newProviders) {
 144                     System.out.println("PKCS11Test: remove new Provider: " + p.getName());
 145                     Security.removeProvider(p.getName());
 146                 }
 147                 for (Provider p: oldProviders) {
 148                     System.out.println("PKCS11Test: add old Provider: " + p.getName());
 149                     Security.addProvider(p);
 150                 }
 151             }
 152         }
 153     }
 154 
 155     public static void testDeimos(PKCS11Test test) throws Exception {
 156         if (new File("/opt/SUNWconn/lib/libpkcs11.so").isFile() == false ||
 157             "true".equals(System.getProperty("NO_DEIMOS"))) {
 158             return;
 159         }
 160         String base = getBase();
 161         String p11config = base + SEP + "nss" + SEP + "p11-deimos.txt";
 162         Provider p = getSunPKCS11(p11config);
 163         test.premain(p);
 164     }
 165 
 166     public static void testDefault(PKCS11Test test) throws Exception {
 167         // run test for default configured PKCS11 providers (if any)
 168 
 169         if ("true".equals(System.getProperty("NO_DEFAULT"))) {
 170             return;
 171         }
 172 
 173         Provider[] providers = Security.getProviders();
 174         for (int i = 0; i < providers.length; i++) {
 175             Provider p = providers[i];
 176             if (p.getName().startsWith("SunPKCS11-")) {
 177                 test.premain(p);
 178             }
 179         }
 180     }
 181 
 182     private static String PKCS11_BASE;
 183     static {
 184         try {
 185             PKCS11_BASE = getBase();
 186         } catch (Exception e) {
 187             // ignore
 188         }
 189     }
 190 
 191     private final static String PKCS11_REL_PATH = "sun/security/pkcs11";
 192 
 193     public static String getBase() throws Exception {
 194         if (PKCS11_BASE != null) {
 195             return PKCS11_BASE;
 196         }
 197         File cwd = new File(System.getProperty("test.src", ".")).getCanonicalFile();
 198         while (true) {
 199             File file = new File(cwd, "TEST.ROOT");
 200             if (file.isFile()) {
 201                 break;
 202             }
 203             cwd = cwd.getParentFile();
 204             if (cwd == null) {
 205                 throw new Exception("Test root directory not found");
 206             }
 207         }
 208         PKCS11_BASE = new File(cwd, PKCS11_REL_PATH.replace('/', SEP)).getAbsolutePath();
 209         return PKCS11_BASE;
 210     }
 211 
 212     public static String getNSSLibDir() throws Exception {
 213         return getNSSLibDir(nss_library);
 214     }
 215 
 216     static String getNSSLibDir(String library) throws Exception {
 217         Properties props = System.getProperties();
 218         String osName = props.getProperty("os.name");
 219         if (osName.startsWith("Win")) {
 220             osName = "Windows";
 221             NSPR_PREFIX = "lib";
 222         } else if (osName.equals("Mac OS X")) {
 223             osName = "MacOSX";
 224         }
 225         String osid = osName + "-"
 226                 + props.getProperty("os.arch") + "-" + props.getProperty("sun.arch.data.model");
 227         String[] nssLibDirs = osMap.get(osid);
 228         if (nssLibDirs == null) {
 229             System.out.println("Unsupported OS, skipping: " + osid);
 230             return null;
 231         }
 232         if (nssLibDirs.length == 0) {
 233             System.out.println("NSS not supported on this platform, skipping test");
 234             return null;
 235         }
 236         String nssLibDir = null;
 237         for (String dir : nssLibDirs) {
 238             if (new File(dir).exists() &&
 239                 new File(dir + System.mapLibraryName(library)).exists()) {
 240                 nssLibDir = dir;
 241                 System.setProperty("pkcs11test.nss.libdir", nssLibDir);
 242                 break;
 243             }
 244         }
 245         return nssLibDir;
 246     }
 247 
 248     protected static void safeReload(String lib) throws Exception {
 249         try {
 250             System.load(lib);
 251         } catch (UnsatisfiedLinkError e) {
 252             if (e.getMessage().contains("already loaded")) {
 253                 return;
 254             }
 255         }
 256     }
 257 
 258     static boolean loadNSPR(String libdir) throws Exception {
 259         // load NSS softoken dependencies in advance to avoid resolver issues
 260         safeReload(libdir + System.mapLibraryName(NSPR_PREFIX + "nspr4"));
 261         safeReload(libdir + System.mapLibraryName(NSPR_PREFIX + "plc4"));
 262         safeReload(libdir + System.mapLibraryName(NSPR_PREFIX + "plds4"));
 263         safeReload(libdir + System.mapLibraryName("sqlite3"));
 264         safeReload(libdir + System.mapLibraryName("nssutil3"));
 265         return true;
 266     }
 267 
 268     // Check the provider being used is NSS
 269     public static boolean isNSS(Provider p) {
 270         return p.getName().toUpperCase().equals("SUNPKCS11-NSS");
 271     }
 272 
 273     static double getNSSVersion() {
 274         if (nss_version == -1)
 275             getNSSInfo();
 276         return nss_version;
 277     }
 278 
 279     static ECCState getNSSECC() {
 280         if (nss_version == -1)
 281             getNSSInfo();
 282         return nss_ecc_status;
 283     }
 284 
 285     public static double getLibsoftokn3Version() {
 286         if (softoken3_version == -1)
 287             return getNSSInfo("softokn3");
 288         return softoken3_version;
 289     }
 290 
 291     public static double getLibnss3Version() {
 292         if (nss3_version == -1)
 293             return getNSSInfo("nss3");
 294         return nss3_version;
 295     }
 296 
 297     /* Read the library to find out the verison */
 298     static void getNSSInfo() {
 299         getNSSInfo(nss_library);
 300     }
 301 
 302     static double getNSSInfo(String library) {
 303         String nssHeader = "$Header: NSS";
 304         boolean found = false;
 305         String s = null;
 306         int i = 0;
 307         String libfile = "";
 308 
 309         if (library.compareTo("softokn3") == 0 && softoken3_version > -1)
 310             return softoken3_version;
 311         if (library.compareTo("nss3") == 0 && nss3_version > -1)
 312             return nss3_version;
 313 
 314         try {
 315             libfile = getNSSLibDir() + System.mapLibraryName(library);
 316             FileInputStream is = new FileInputStream(libfile);
 317             byte[] data = new byte[1000];
 318             int read = 0;
 319 
 320             while (is.available() > 0) {
 321                 if (read == 0) {
 322                     read = is.read(data, 0, 1000);
 323                 } else {
 324                     // Prepend last 100 bytes in case the header was split
 325                     // between the reads.
 326                     System.arraycopy(data, 900, data, 0, 100);
 327                     read = 100 + is.read(data, 100, 900);
 328                 }
 329 
 330                 s = new String(data, 0, read);
 331                 if ((i = s.indexOf(nssHeader)) > 0) {
 332                     found = true;
 333                     // If the nssHeader is before 920 we can break, otherwise
 334                     // we may not have the whole header so do another read.  If
 335                     // no bytes are in the stream, that is ok, found is true.
 336                     if (i < 920) {
 337                         break;
 338                     }
 339                 }
 340             }
 341 
 342             is.close();
 343 
 344         } catch (Exception e) {
 345             e.printStackTrace();
 346         }
 347 
 348         if (!found) {
 349             System.out.println("lib" + library +
 350                     " version not found, set to 0.0: " + libfile);
 351             nss_version = 0.0;
 352             return nss_version;
 353         }
 354 
 355         // the index after whitespace after nssHeader
 356         int afterheader = s.indexOf("NSS", i) + 4;
 357         String version = s.substring(afterheader, s.indexOf(' ', afterheader));
 358 
 359         // If a "dot dot" release, strip the extra dots for double parsing
 360         String[] dot = version.split("\\.");
 361         if (dot.length > 2) {
 362             version = dot[0]+"."+dot[1];
 363             for (int j = 2; dot.length > j; j++) {
 364                 version += dot[j];
 365             }
 366         }
 367 
 368         // Convert to double for easier version value checking
 369         try {
 370             nss_version = Double.parseDouble(version);
 371         } catch (NumberFormatException e) {
 372             System.out.println("Failed to parse lib" + library +
 373                     " version. Set to 0.0");
 374             e.printStackTrace();
 375         }
 376 
 377         System.out.print("lib" + library + " version = "+version+".  ");
 378 
 379         // Check for ECC
 380         if (s.indexOf("Basic") > 0) {
 381             nss_ecc_status = ECCState.Basic;
 382             System.out.println("ECC Basic.");
 383         } else if (s.indexOf("Extended") > 0) {
 384             nss_ecc_status = ECCState.Extended;
 385             System.out.println("ECC Extended.");
 386         } else {
 387             System.out.println("ECC None.");
 388         }
 389 
 390         if (library.compareTo("softokn3") == 0) {
 391             softoken3_version = nss_version;
 392         } else if (library.compareTo("nss3") == 0) {
 393             nss3_version = nss_version;
 394         }
 395 
 396         return nss_version;
 397     }
 398 
 399     // Used to set the nss_library file to search for libsoftokn3.so
 400     public static void useNSS() {
 401         nss_library = "nss3";
 402     }
 403 
 404     public static void testNSS(PKCS11Test test) throws Exception {
 405         String libdir = getNSSLibDir();
 406         if (libdir == null) {
 407             return;
 408         }
 409         String base = getBase();
 410 
 411         if (loadNSPR(libdir) == false) {
 412             return;
 413         }
 414 
 415         String libfile = libdir + System.mapLibraryName(nss_library);
 416 
 417         String customDBdir = System.getProperty("CUSTOM_DB_DIR");
 418         String dbdir = (customDBdir != null) ?
 419                                 customDBdir :
 420                                 base + SEP + "nss" + SEP + "db";
 421         // NSS always wants forward slashes for the config path
 422         dbdir = dbdir.replace('\\', '/');
 423 
 424         String customConfig = System.getProperty("CUSTOM_P11_CONFIG");
 425         String customConfigName = System.getProperty("CUSTOM_P11_CONFIG_NAME", "p11-nss.txt");
 426         String p11config = (customConfig != null) ?
 427                                 customConfig :
 428                                 base + SEP + "nss" + SEP + customConfigName;
 429 
 430         System.setProperty("pkcs11test.nss.lib", libfile);
 431         System.setProperty("pkcs11test.nss.db", dbdir);
 432         Provider p = getSunPKCS11(p11config);
 433         test.premain(p);
 434     }
 435 
 436     // Generate a vector of supported elliptic curves of a given provider
 437     static Vector<ECParameterSpec> getKnownCurves(Provider p) throws Exception {
 438         int index;
 439         int begin;
 440         int end;
 441         String curve;
 442         KeyPair kp = null;
 443 
 444         Vector<ECParameterSpec> results = new Vector<ECParameterSpec>();
 445         // Get Curves to test from SunEC.
 446         String kcProp = Security.getProvider("SunEC").
 447                 getProperty("AlgorithmParameters.EC SupportedCurves");
 448 
 449         if (kcProp == null) {
 450             throw new RuntimeException(
 451             "\"AlgorithmParameters.EC SupportedCurves property\" not found");
 452         }
 453 
 454         System.out.println("Finding supported curves using list from SunEC\n");
 455         index = 0;
 456         for (;;) {
 457             // Each set of curve names is enclosed with brackets.
 458             begin = kcProp.indexOf('[', index);
 459             end = kcProp.indexOf(']', index);
 460             if (begin == -1 || end == -1) {
 461                 break;
 462             }
 463 
 464             /*
 465              * Each name is separated by a comma.
 466              * Just get the first name in the set.
 467              */
 468             index = end + 1;
 469             begin++;
 470             end = kcProp.indexOf(',', begin);
 471             if (end == -1) {
 472                 // Only one name in the set.
 473                 end = index -1;
 474             }
 475 
 476             curve = kcProp.substring(begin, end);
 477             ECParameterSpec e = getECParameterSpec(p, curve);
 478             System.out.print("\t "+ curve + ": ");
 479             try {
 480                 KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", p);
 481                 kpg.initialize(e);
 482                 kp = kpg.generateKeyPair();
 483                 results.add(e);
 484                 System.out.println("Supported");
 485             } catch (ProviderException ex) {
 486                 System.out.println("Unsupported: PKCS11: " +
 487                         ex.getCause().getMessage());
 488             } catch (InvalidAlgorithmParameterException ex) {
 489                 System.out.println("Unsupported: Key Length: " +
 490                         ex.getMessage());
 491             }
 492         }
 493 
 494         if (results.size() == 0) {
 495             throw new RuntimeException("No supported EC curves found");
 496         }
 497 
 498         return results;
 499     }
 500 
 501     private static ECParameterSpec getECParameterSpec(Provider p, String name)
 502             throws Exception {
 503 
 504         AlgorithmParameters parameters =
 505             AlgorithmParameters.getInstance("EC", p);
 506 
 507         parameters.init(new ECGenParameterSpec(name));
 508 
 509         return parameters.getParameterSpec(ECParameterSpec.class);
 510     }
 511 
 512     // Check support for a curve with a provided Vector of EC support
 513     boolean checkSupport(Vector<ECParameterSpec> supportedEC,
 514             ECParameterSpec curve) {
 515         boolean found = false;
 516         for (ECParameterSpec ec: supportedEC) {
 517             if (ec.equals(curve)) {
 518                 return true;
 519             }
 520         }
 521         return false;
 522     }
 523 
 524     private static final Map<String,String[]> osMap;
 525 
 526     // Location of the NSS libraries on each supported platform
 527     static {
 528         osMap = new HashMap<String,String[]>();
 529         osMap.put("SunOS-sparc-32", new String[]{"/usr/lib/mps/"});
 530         osMap.put("SunOS-sparcv9-64", new String[]{"/usr/lib/mps/64/"});
 531         osMap.put("SunOS-x86-32", new String[]{"/usr/lib/mps/"});
 532         osMap.put("SunOS-amd64-64", new String[]{"/usr/lib/mps/64/"});
 533         osMap.put("Linux-i386-32", new String[]{
 534             "/usr/lib/i386-linux-gnu/", "/usr/lib32/", "/usr/lib/"});
 535         osMap.put("Linux-amd64-64", new String[]{
 536             "/usr/lib/x86_64-linux-gnu/", "/usr/lib/x86_64-linux-gnu/nss/",
 537             "/usr/lib64/"});
 538         osMap.put("Windows-x86-32", new String[]{
 539             PKCS11_BASE + "/nss/lib/windows-i586/".replace('/', SEP)});
 540         osMap.put("Windows-amd64-64", new String[]{
 541             PKCS11_BASE + "/nss/lib/windows-amd64/".replace('/', SEP)});
 542         osMap.put("MacOSX-x86_64-64", new String[]{
 543             PKCS11_BASE + "/nss/lib/macosx-x86_64/"});
 544     }
 545 
 546     private final static char[] hexDigits = "0123456789abcdef".toCharArray();
 547 
 548     public static String toString(byte[] b) {
 549         if (b == null) {
 550             return "(null)";
 551         }
 552         StringBuffer sb = new StringBuffer(b.length * 3);
 553         for (int i = 0; i < b.length; i++) {
 554             int k = b[i] & 0xff;
 555             if (i != 0) {
 556                 sb.append(':');
 557             }
 558             sb.append(hexDigits[k >>> 4]);
 559             sb.append(hexDigits[k & 0xf]);
 560         }
 561         return sb.toString();
 562     }
 563 
 564     public static byte[] parse(String s) {
 565         if (s.equals("(null)")) {
 566             return null;
 567         }
 568         try {
 569             int n = s.length();
 570             ByteArrayOutputStream out = new ByteArrayOutputStream(n / 3);
 571             StringReader r = new StringReader(s);
 572             while (true) {
 573                 int b1 = nextNibble(r);
 574                 if (b1 < 0) {
 575                     break;
 576                 }
 577                 int b2 = nextNibble(r);
 578                 if (b2 < 0) {
 579                     throw new RuntimeException("Invalid string " + s);
 580                 }
 581                 int b = (b1 << 4) | b2;
 582                 out.write(b);
 583             }
 584             return out.toByteArray();
 585         } catch (IOException e) {
 586             throw new RuntimeException(e);
 587         }
 588     }
 589 
 590     private static int nextNibble(StringReader r) throws IOException {
 591         while (true) {
 592             int ch = r.read();
 593             if (ch == -1) {
 594                 return -1;
 595             } else if ((ch >= '0') && (ch <= '9')) {
 596                 return ch - '0';
 597             } else if ((ch >= 'a') && (ch <= 'f')) {
 598                 return ch - 'a' + 10;
 599             } else if ((ch >= 'A') && (ch <= 'F')) {
 600                 return ch - 'A' + 10;
 601             }
 602         }
 603     }
 604 
 605     <T> T[] concat(T[] a, T[] b) {
 606         if ((b == null) || (b.length == 0)) {
 607             return a;
 608         }
 609         T[] r = Arrays.copyOf(a, a.length + b.length);
 610         System.arraycopy(b, 0, r, a.length, b.length);
 611         return r;
 612     }
 613 
 614     /**
 615      * Returns supported algorithms of specified type.
 616      */
 617     static List<String> getSupportedAlgorithms(String type, String alg,
 618             Provider p) {
 619         // prepare a list of supported algorithms
 620         List<String> algorithms = new ArrayList<>();
 621         Set<Provider.Service> services = p.getServices();
 622         for (Provider.Service service : services) {
 623             if (service.getType().equals(type)
 624                     && service.getAlgorithm().startsWith(alg)) {
 625                 algorithms.add(service.getAlgorithm());
 626             }
 627         }
 628         return algorithms;
 629     }
 630 
 631 }