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