1 /* 2 * Copyright (c) 1997, 2017, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.security; 27 28 29 import java.net.URL; 30 import java.net.SocketPermission; 31 import java.util.ArrayList; 32 import java.util.List; 33 import java.util.Hashtable; 34 import java.io.ByteArrayInputStream; 35 import java.io.IOException; 36 import java.security.cert.*; 37 import sun.misc.IOUtils; 38 39 /** 40 * 41 * <p>This class extends the concept of a codebase to 42 * encapsulate not only the location (URL) but also the certificate chains 43 * that were used to verify signed code originating from that location. 44 * 45 * @author Li Gong 46 * @author Roland Schemers 47 */ 48 49 public class CodeSource implements java.io.Serializable { 50 51 private static final long serialVersionUID = 4977541819976013951L; 52 53 /** 54 * The code location. 55 * 56 * @serial 57 */ 58 private URL location; 59 60 /* 61 * The code signers. 62 */ 63 private transient CodeSigner[] signers = null; 64 65 /* 66 * The code signers. Certificate chains are concatenated. 67 */ 68 private transient java.security.cert.Certificate certs[] = null; 69 70 // cached SocketPermission used for matchLocation 71 private transient SocketPermission sp; 72 73 // for generating cert paths 74 private transient CertificateFactory factory = null; 75 76 /** 77 * Constructs a CodeSource and associates it with the specified 78 * location and set of certificates. 79 * 80 * @param url the location (URL). 81 * 82 * @param certs the certificate(s). It may be null. The contents of the 83 * array are copied to protect against subsequent modification. 84 */ 85 public CodeSource(URL url, java.security.cert.Certificate certs[]) { 86 this.location = url; 87 88 // Copy the supplied certs 89 if (certs != null) { 90 this.certs = certs.clone(); 91 } 92 } 93 94 /** 95 * Constructs a CodeSource and associates it with the specified 96 * location and set of code signers. 97 * 98 * @param url the location (URL). 99 * @param signers the code signers. It may be null. The contents of the 100 * array are copied to protect against subsequent modification. 101 * 102 * @since 1.5 103 */ 104 public CodeSource(URL url, CodeSigner[] signers) { 105 this.location = url; 106 107 // Copy the supplied signers 108 if (signers != null) { 109 this.signers = signers.clone(); 110 } 111 } 112 113 /** 114 * Returns the hash code value for this object. 115 * 116 * @return a hash code value for this object. 117 */ 118 @Override 119 public int hashCode() { 120 if (location != null) 121 return location.hashCode(); 122 else 123 return 0; 124 } 125 126 /** 127 * Tests for equality between the specified object and this 128 * object. Two CodeSource objects are considered equal if their 129 * locations are of identical value and if their signer certificate 130 * chains are of identical value. It is not required that 131 * the certificate chains be in the same order. 132 * 133 * @param obj the object to test for equality with this object. 134 * 135 * @return true if the objects are considered equal, false otherwise. 136 */ 137 @Override 138 public boolean equals(Object obj) { 139 if (obj == this) 140 return true; 141 142 // objects types must be equal 143 if (!(obj instanceof CodeSource)) 144 return false; 145 146 CodeSource cs = (CodeSource) obj; 147 148 // URLs must match 149 if (location == null) { 150 // if location is null, then cs.location must be null as well 151 if (cs.location != null) return false; 152 } else { 153 // if location is not null, then it must equal cs.location 154 if (!location.equals(cs.location)) return false; 155 } 156 157 // certs must match 158 return matchCerts(cs, true); 159 } 160 161 /** 162 * Returns the location associated with this CodeSource. 163 * 164 * @return the location (URL). 165 */ 166 public final URL getLocation() { 167 /* since URL is practically immutable, returning itself is not 168 a security problem */ 169 return this.location; 170 } 171 172 /** 173 * Returns the certificates associated with this CodeSource. 174 * <p> 175 * If this CodeSource object was created using the 176 * {@link #CodeSource(URL url, CodeSigner[] signers)} 177 * constructor then its certificate chains are extracted and used to 178 * create an array of Certificate objects. Each signer certificate is 179 * followed by its supporting certificate chain (which may be empty). 180 * Each signer certificate and its supporting certificate chain is ordered 181 * bottom-to-top (i.e., with the signer certificate first and the (root) 182 * certificate authority last). 183 * 184 * @return A copy of the certificates array, or null if there is none. 185 */ 186 public final java.security.cert.Certificate[] getCertificates() { 187 if (certs != null) { 188 return certs.clone(); 189 190 } else if (signers != null) { 191 // Convert the code signers to certs 192 ArrayList<java.security.cert.Certificate> certChains = 193 new ArrayList<>(); 194 for (int i = 0; i < signers.length; i++) { 195 certChains.addAll( 196 signers[i].getSignerCertPath().getCertificates()); 197 } 198 certs = certChains.toArray( 199 new java.security.cert.Certificate[certChains.size()]); 200 return certs.clone(); 201 202 } else { 203 return null; 204 } 205 } 206 207 /** 208 * Returns the code signers associated with this CodeSource. 209 * <p> 210 * If this CodeSource object was created using the 211 * {@link #CodeSource(URL url, java.security.cert.Certificate[] certs)} 212 * constructor then its certificate chains are extracted and used to 213 * create an array of CodeSigner objects. Note that only X.509 certificates 214 * are examined - all other certificate types are ignored. 215 * 216 * @return A copy of the code signer array, or null if there is none. 217 * 218 * @since 1.5 219 */ 220 public final CodeSigner[] getCodeSigners() { 221 if (signers != null) { 222 return signers.clone(); 223 224 } else if (certs != null) { 225 // Convert the certs to code signers 226 signers = convertCertArrayToSignerArray(certs); 227 return signers.clone(); 228 229 } else { 230 return null; 231 } 232 } 233 234 /** 235 * Returns true if this CodeSource object "implies" the specified CodeSource. 236 * <p> 237 * More specifically, this method makes the following checks. 238 * If any fail, it returns false. If they all succeed, it returns true. 239 * <ul> 240 * <li> <i>codesource</i> must not be null. 241 * <li> If this object's certificates are not null, then all 242 * of this object's certificates must be present in <i>codesource</i>'s 243 * certificates. 244 * <li> If this object's location (getLocation()) is not null, then the 245 * following checks are made against this object's location and 246 * <i>codesource</i>'s: 247 * <ul> 248 * <li> <i>codesource</i>'s location must not be null. 249 * 250 * <li> If this object's location 251 * equals <i>codesource</i>'s location, then return true. 252 * 253 * <li> This object's protocol (getLocation().getProtocol()) must be 254 * equal to <i>codesource</i>'s protocol, ignoring case. 255 * 256 * <li> If this object's host (getLocation().getHost()) is not null, 257 * then the SocketPermission 258 * constructed with this object's host must imply the 259 * SocketPermission constructed with <i>codesource</i>'s host. 260 * 261 * <li> If this object's port (getLocation().getPort()) is not 262 * equal to -1 (that is, if a port is specified), it must equal 263 * <i>codesource</i>'s port or default port 264 * (codesource.getLocation().getDefaultPort()). 265 * 266 * <li> If this object's file (getLocation().getFile()) doesn't equal 267 * <i>codesource</i>'s file, then the following checks are made: 268 * If this object's file ends with "/-", 269 * then <i>codesource</i>'s file must start with this object's 270 * file (exclusive the trailing "-"). 271 * If this object's file ends with a "/*", 272 * then <i>codesource</i>'s file must start with this object's 273 * file and must not have any further "/" separators. 274 * If this object's file doesn't end with a "/", 275 * then <i>codesource</i>'s file must match this object's 276 * file with a '/' appended. 277 * 278 * <li> If this object's reference (getLocation().getRef()) is 279 * not null, it must equal <i>codesource</i>'s reference. 280 * 281 * </ul> 282 * </ul> 283 * <p> 284 * For example, the codesource objects with the following locations 285 * and null certificates all imply 286 * the codesource with the location "http://java.sun.com/classes/foo.jar" 287 * and null certificates: 288 * <pre> 289 * http: 290 * http://*.sun.com/classes/* 291 * http://java.sun.com/classes/- 292 * http://java.sun.com/classes/foo.jar 293 * </pre> 294 * 295 * Note that if this CodeSource has a null location and a null 296 * certificate chain, then it implies every other CodeSource. 297 * 298 * @param codesource CodeSource to compare against. 299 * 300 * @return true if the specified codesource is implied by this codesource, 301 * false if not. 302 */ 303 304 public boolean implies(CodeSource codesource) 305 { 306 if (codesource == null) 307 return false; 308 309 return matchCerts(codesource, false) && matchLocation(codesource); 310 } 311 312 /** 313 * Returns true if all the certs in this 314 * CodeSource are also in <i>that</i>. 315 * 316 * @param that the CodeSource to check against. 317 * @param strict If true then a strict equality match is performed. 318 * Otherwise a subset match is performed. 319 */ 320 private boolean matchCerts(CodeSource that, boolean strict) 321 { 322 boolean match; 323 324 // match any key 325 if (certs == null && signers == null) { 326 if (strict) { 327 return (that.certs == null && that.signers == null); 328 } else { 329 return true; 330 } 331 // both have signers 332 } else if (signers != null && that.signers != null) { 333 if (strict && signers.length != that.signers.length) { 334 return false; 335 } 336 for (int i = 0; i < signers.length; i++) { 337 match = false; 338 for (int j = 0; j < that.signers.length; j++) { 339 if (signers[i].equals(that.signers[j])) { 340 match = true; 341 break; 342 } 343 } 344 if (!match) return false; 345 } 346 return true; 347 348 // both have certs 349 } else if (certs != null && that.certs != null) { 350 if (strict && certs.length != that.certs.length) { 351 return false; 352 } 353 for (int i = 0; i < certs.length; i++) { 354 match = false; 355 for (int j = 0; j < that.certs.length; j++) { 356 if (certs[i].equals(that.certs[j])) { 357 match = true; 358 break; 359 } 360 } 361 if (!match) return false; 362 } 363 return true; 364 } 365 366 return false; 367 } 368 369 370 /** 371 * Returns true if two CodeSource's have the "same" location. 372 * 373 * @param that CodeSource to compare against 374 */ 375 private boolean matchLocation(CodeSource that) { 376 if (location == null) 377 return true; 378 379 if ((that == null) || (that.location == null)) 380 return false; 381 382 if (location.equals(that.location)) 383 return true; 384 385 if (!location.getProtocol().equalsIgnoreCase(that.location.getProtocol())) 386 return false; 387 388 int thisPort = location.getPort(); 389 if (thisPort != -1) { 390 int thatPort = that.location.getPort(); 391 int port = thatPort != -1 ? thatPort 392 : that.location.getDefaultPort(); 393 if (thisPort != port) 394 return false; 395 } 396 397 if (location.getFile().endsWith("/-")) { 398 // Matches the directory and (recursively) all files 399 // and subdirectories contained in that directory. 400 // For example, "/a/b/-" implies anything that starts with 401 // "/a/b/" 402 String thisPath = location.getFile().substring(0, 403 location.getFile().length()-1); 404 if (!that.location.getFile().startsWith(thisPath)) 405 return false; 406 } else if (location.getFile().endsWith("/*")) { 407 // Matches the directory and all the files contained in that 408 // directory. 409 // For example, "/a/b/*" implies anything that starts with 410 // "/a/b/" but has no further slashes 411 int last = that.location.getFile().lastIndexOf('/'); 412 if (last == -1) 413 return false; 414 String thisPath = location.getFile().substring(0, 415 location.getFile().length()-1); 416 String thatPath = that.location.getFile().substring(0, last+1); 417 if (!thatPath.equals(thisPath)) 418 return false; 419 } else { 420 // Exact matches only. 421 // For example, "/a/b" and "/a/b/" both imply "/a/b/" 422 if ((!that.location.getFile().equals(location.getFile())) 423 && (!that.location.getFile().equals(location.getFile()+"/"))) { 424 return false; 425 } 426 } 427 428 if (location.getRef() != null 429 && !location.getRef().equals(that.location.getRef())) { 430 return false; 431 } 432 433 String thisHost = location.getHost(); 434 String thatHost = that.location.getHost(); 435 if (thisHost != null) { 436 if (("".equals(thisHost) || "localhost".equals(thisHost)) && 437 ("".equals(thatHost) || "localhost".equals(thatHost))) { 438 // ok 439 } else if (!thisHost.equals(thatHost)) { 440 if (thatHost == null) { 441 return false; 442 } 443 if (this.sp == null) { 444 this.sp = new SocketPermission(thisHost, "resolve"); 445 } 446 if (that.sp == null) { 447 that.sp = new SocketPermission(thatHost, "resolve"); 448 } 449 if (!this.sp.implies(that.sp)) { 450 return false; 451 } 452 } 453 } 454 // everything matches 455 return true; 456 } 457 458 /** 459 * Returns a string describing this CodeSource, telling its 460 * URL and certificates. 461 * 462 * @return information about this CodeSource. 463 */ 464 @Override 465 public String toString() { 466 StringBuilder sb = new StringBuilder(); 467 sb.append("("); 468 sb.append(this.location); 469 470 if (this.certs != null && this.certs.length > 0) { 471 for (int i = 0; i < this.certs.length; i++) { 472 sb.append( " " + this.certs[i]); 473 } 474 475 } else if (this.signers != null && this.signers.length > 0) { 476 for (int i = 0; i < this.signers.length; i++) { 477 sb.append( " " + this.signers[i]); 478 } 479 } else { 480 sb.append(" <no signer certificates>"); 481 } 482 sb.append(")"); 483 return sb.toString(); 484 } 485 486 /** 487 * Writes this object out to a stream (i.e., serializes it). 488 * 489 * @serialData An initial {@code URL} is followed by an 490 * {@code int} indicating the number of certificates to follow 491 * (a value of "zero" denotes that there are no certificates associated 492 * with this object). 493 * Each certificate is written out starting with a {@code String} 494 * denoting the certificate type, followed by an 495 * {@code int} specifying the length of the certificate encoding, 496 * followed by the certificate encoding itself which is written out as an 497 * array of bytes. Finally, if any code signers are present then the array 498 * of code signers is serialized and written out too. 499 */ 500 private void writeObject(java.io.ObjectOutputStream oos) 501 throws IOException 502 { 503 oos.defaultWriteObject(); // location 504 505 // Serialize the array of certs 506 if (certs == null || certs.length == 0) { 507 oos.writeInt(0); 508 } else { 509 // write out the total number of certs 510 oos.writeInt(certs.length); 511 // write out each cert, including its type 512 for (int i = 0; i < certs.length; i++) { 513 java.security.cert.Certificate cert = certs[i]; 514 try { 515 oos.writeUTF(cert.getType()); 516 byte[] encoded = cert.getEncoded(); 517 oos.writeInt(encoded.length); 518 oos.write(encoded); 519 } catch (CertificateEncodingException cee) { 520 throw new IOException(cee.getMessage()); 521 } 522 } 523 } 524 525 // Serialize the array of code signers (if any) 526 if (signers != null && signers.length > 0) { 527 oos.writeObject(signers); 528 } 529 } 530 531 /** 532 * Restores this object from a stream (i.e., deserializes it). 533 */ 534 private void readObject(java.io.ObjectInputStream ois) 535 throws IOException, ClassNotFoundException 536 { 537 CertificateFactory cf; 538 Hashtable<String, CertificateFactory> cfs = null; 539 List<java.security.cert.Certificate> certList = null; 540 541 ois.defaultReadObject(); // location 542 543 // process any new-style certs in the stream (if present) 544 int size = ois.readInt(); 545 if (size > 0) { 546 // we know of 3 different cert types: X.509, PGP, SDSI, which 547 // could all be present in the stream at the same time 548 cfs = new Hashtable<String, CertificateFactory>(3); 549 certList = new ArrayList<>(size > 20 ? 20 : size); 550 } else if (size < 0) { 551 throw new IOException("size cannot be negative"); 552 } 553 554 for (int i = 0; i < size; i++) { 555 // read the certificate type, and instantiate a certificate 556 // factory of that type (reuse existing factory if possible) 557 String certType = ois.readUTF(); 558 if (cfs.containsKey(certType)) { 559 // reuse certificate factory 560 cf = cfs.get(certType); 561 } else { 562 // create new certificate factory 563 try { 564 cf = CertificateFactory.getInstance(certType); 565 } catch (CertificateException ce) { 566 throw new ClassNotFoundException 567 ("Certificate factory for " + certType + " not found"); 568 } 569 // store the certificate factory so we can reuse it later 570 cfs.put(certType, cf); 571 } 572 // parse the certificate 573 byte[] encoded = IOUtils.readNBytes(ois, ois.readInt()); 574 ByteArrayInputStream bais = new ByteArrayInputStream(encoded); 575 try { 576 certList.add(cf.generateCertificate(bais)); 577 } catch (CertificateException ce) { 578 throw new IOException(ce.getMessage()); 579 } 580 bais.close(); 581 } 582 583 if (certList != null) { 584 this.certs = certList.toArray( 585 new java.security.cert.Certificate[size]); 586 } 587 // Deserialize array of code signers (if any) 588 try { 589 this.signers = ((CodeSigner[])ois.readObject()).clone(); 590 } catch (IOException ioe) { 591 // no signers present 592 } 593 } 594 595 /* 596 * Convert an array of certificates to an array of code signers. 597 * The array of certificates is a concatenation of certificate chains 598 * where the initial certificate in each chain is the end-entity cert. 599 * 600 * @return An array of code signers or null if none are generated. 601 */ 602 private CodeSigner[] convertCertArrayToSignerArray( 603 java.security.cert.Certificate[] certs) { 604 605 if (certs == null) { 606 return null; 607 } 608 609 try { 610 // Initialize certificate factory 611 if (factory == null) { 612 factory = CertificateFactory.getInstance("X.509"); 613 } 614 615 // Iterate through all the certificates 616 int i = 0; 617 List<CodeSigner> signers = new ArrayList<>(); 618 while (i < certs.length) { 619 List<java.security.cert.Certificate> certChain = 620 new ArrayList<>(); 621 certChain.add(certs[i++]); // first cert is an end-entity cert 622 int j = i; 623 624 // Extract chain of certificates 625 // (loop while certs are not end-entity certs) 626 while (j < certs.length && 627 certs[j] instanceof X509Certificate && 628 ((X509Certificate)certs[j]).getBasicConstraints() != -1) { 629 certChain.add(certs[j]); 630 j++; 631 } 632 i = j; 633 CertPath certPath = factory.generateCertPath(certChain); 634 signers.add(new CodeSigner(certPath, null)); 635 } 636 637 if (signers.isEmpty()) { 638 return null; 639 } else { 640 return signers.toArray(new CodeSigner[signers.size()]); 641 } 642 643 } catch (CertificateException e) { 644 return null; //TODO - may be better to throw an ex. here 645 } 646 } 647 }