blob: 0865a96d6dec87ca97f599387cd9cd68bb2057a3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package java.security;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import org.apache.harmony.security.fortress.PolicyUtils;
/**
* An {@code UnresolvedPermission} represents a {@code Permission} whose type
* should be resolved lazy and not during initialization time of the {@code
* Policy}. {@code UnresolvedPermission}s contain all information to be replaced
* by a concrete typed {@code Permission} right before the access checks are
* performed.
*/
public final class UnresolvedPermission extends Permission
implements Serializable {
private static final long serialVersionUID = -4821973115467008846L;
private String type;
private String name;
private String actions;
// The signer certificates
private transient Certificate[] targetCerts;
// Cached hash value
private transient int hash;
/**
* Constructs a new instance of {@code UnresolvedPermission}. The supplied
* parameters are used when this instance is resolved to the concrete
* {@code Permission}.
*
* @param type
* the fully qualified class name of the permission this class is
* resolved to.
* @param name
* the name of the permission this class is resolved to, maybe
* {@code null}.
* @param actions
* the actions of the permission this class is resolved to, maybe
* {@code null}.
* @param certs
* the certificates of the permission this class is resolved to,
* maybe {@code null}.
* @throws NullPointerException
* if type is {@code null}.
*/
public UnresolvedPermission(String type, String name, String actions,
Certificate[] certs) {
super(type);
checkType(type);
this.type = type;
this.name = name;
this.actions = actions;
if (certs != null) {
this.targetCerts = new Certificate[certs.length];
System.arraycopy(certs, 0, targetCerts, 0, certs.length);
}
hash = 0;
}
// Check type parameter
private final void checkType(String type) {
if (type == null) {
throw new NullPointerException("type == null");
}
// type is the class name of the Permission class.
// Empty string is inappropriate for class name.
// But this check is commented out for compatibility with RI.
// see JIRA issue HARMONY-733
// if (type.length() == 0) {
// throw new IllegalArgumentException("type cannot be empty");
// }
}
/**
* Compares the specified object with this {@code UnresolvedPermission} for
* equality and returns {@code true} if the specified object is equal,
* {@code false} otherwise. To be equal, the specified object needs to be an
* instance of {@code UnresolvedPermission}, the two {@code
* UnresolvedPermission}s must refer to the same type and must have the same
* name, the same actions and certificates.
*
* @param obj
* object to be compared for equality with this {@code
* UnresolvedPermission}.
* @return {@code true} if the specified object is equal to this {@code
* UnresolvedPermission}, otherwise {@code false}.
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof UnresolvedPermission) {
UnresolvedPermission that = (UnresolvedPermission) obj;
if (getName().equals(that.getName())
&& (name == null ? that.name == null : name
.equals(that.name))
&& (actions == null ? that.actions == null : actions
.equals(that.actions))
&& equalsCertificates(this.targetCerts, that.targetCerts)) {
return true;
}
}
return false;
}
/*
* check whether given array of certificates are equivalent
*/
private boolean equalsCertificates(Certificate[] certs1,
Certificate[] certs2) {
if (certs1 == null || certs2 == null) {
return certs1 == certs2;
}
int length = certs1.length;
if (length != certs2.length) {
return false;
}
if (length > 0) {
boolean found;
for (int i = 0; i < length; i++) {
// Skip the checking for null
if(certs1[i] == null){
continue;
}
found = false;
for (int j = 0; j < length; j++) {
if (certs1[i].equals(certs2[j])) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
for (int i = 0; i < length; i++) {
if(certs2[i] == null){
continue;
}
found = false;
for (int j = 0; j < length; j++) {
if (certs2[i].equals(certs1[j])) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
}
return true;
}
/**
* Returns the hash code value for this {@code UnresolvedPermission}.
* Returns the same hash code for {@code UnresolvedPermission}s that are
* equal to each other as required by the general contract of
* {@link Object#hashCode}.
*
* @return the hash code value for this {@code UnresolvedPermission}.
* @see Object#equals(Object)
* @see UnresolvedPermission#equals(Object)
*/
@Override
public int hashCode() {
if (hash == 0) {
hash = getName().hashCode();
if (name != null) {
hash ^= name.hashCode();
}
if (actions != null) {
hash ^= actions.hashCode();
}
}
return hash;
}
/**
* Returns an empty string since there are no actions allowed for {@code
* UnresolvedPermission}. The actions, specified in the constructor, are
* used when the concrete permission is resolved and created.
*
* @return an empty string, indicating that there are no actions.
*/
@Override
public String getActions() {
return "";
}
/**
* Returns the name of the permission this {@code UnresolvedPermission} is
* resolved to.
*
* @return the name of the permission this {@code UnresolvedPermission} is
* resolved to.
*/
public String getUnresolvedName() {
return name;
}
/**
* Returns the actions of the permission this {@code UnresolvedPermission}
* is resolved to.
*
* @return the actions of the permission this {@code UnresolvedPermission}
* is resolved to.
*/
public String getUnresolvedActions() {
return actions;
}
/**
* Returns the fully qualified class name of the permission this {@code
* UnresolvedPermission} is resolved to.
*
* @return the fully qualified class name of the permission this {@code
* UnresolvedPermission} is resolved to.
*/
public String getUnresolvedType() {
return super.getName();
}
/**
* Returns the certificates of the permission this {@code
* UnresolvedPermission} is resolved to.
*
* @return the certificates of the permission this {@code
* UnresolvedPermission} is resolved to.
*/
public Certificate[] getUnresolvedCerts() {
if (targetCerts != null) {
Certificate[] certs = new Certificate[targetCerts.length];
System.arraycopy(targetCerts, 0, certs, 0, certs.length);
return certs;
}
return null;
}
/**
* Indicates whether the specified permission is implied by this {@code
* UnresolvedPermission}. {@code UnresolvedPermission} objects imply nothing
* since nothing is known about them yet.
* <p>
* Before actual implication checking, this method tries to resolve
* UnresolvedPermissions (if any) against the passed instance. Successfully
* resolved permissions (if any) are taken into account during further
* processing.
*
* @param permission
* the permission to check.
* @return always {@code false}
*/
@Override
public boolean implies(Permission permission) {
return false;
}
/**
* Returns a string containing a concise, human-readable description of this
* {@code UnresolvedPermission} including its target name and its target
* actions.
*
* @return a printable representation for this {@code UnresolvedPermission}.
*/
@Override
public String toString() {
return "(unresolved " + type + " " + name + " "
+ actions + ")";
}
/**
* Returns a new {@code PermissionCollection} for holding {@code
* UnresolvedPermission} objects.
*
* @return a new PermissionCollection for holding {@code
* UnresolvedPermission} objects.
*/
@Override
public PermissionCollection newPermissionCollection() {
return new UnresolvedPermissionCollection();
}
/**
* Tries to resolve this permission into the specified class.
* <p>
* It is assumed that the class has a proper name (as returned by {@code
* getName()} of this unresolved permission), so no check is performed to
* verify this. However, the class must have all required certificates (as
* per {@code getUnresolvedCerts()}) among the passed collection of signers.
* If it does, a zero, one, and/or two-argument constructor is tried to
* instantiate a new permission, which is then returned.
* <p>
* If an appropriate constructor is not available or the class is improperly
* signed, {@code null} is returned.
*
* @param targetType
* - a target class instance, must not be {@code null}
* @return resolved permission or null
*/
Permission resolve(Class targetType) {
// check signers at first
if (PolicyUtils.matchSubset(targetCerts, targetType.getSigners())) {
try {
return PolicyUtils.instantiatePermission(targetType,
name,
actions);
} catch (Exception ignore) {
//TODO log warning?
}
}
return null;
}
/**
* Outputs {@code type},{@code name},{@code actions}
* fields via default mechanism; next manually writes certificates in the
* following format: <br>
*
* <ol>
* <li> int : number of certs or zero </li>
* <li> each cert in the following format
* <ol>
* <li> String : certificate type </li>
* <li> int : length in bytes of certificate </li>
* <li> byte[] : certificate encoding </li>
* </ol>
* </li>
* </ol>
*
* @see <a href="http://java.sun.com/j2se/1.5.0/docs/api/serialized-form.html#java.security.UnresolvedPermission">Java Spec</a>
*/
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
if (targetCerts == null) {
out.writeInt(0);
} else {
out.writeInt(targetCerts.length);
for (int i = 0; i < targetCerts.length; i++) {
try {
byte[] enc = targetCerts[i].getEncoded();
out.writeUTF(targetCerts[i].getType());
out.writeInt(enc.length);
out.write(enc);
} catch (CertificateEncodingException cee) {
throw (IOException) new NotSerializableException("Cannot encode certificate: " + targetCerts[i]).initCause(cee);
}
}
}
}
/**
* Reads the object from stream and checks target type for validity.
*/
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
in.defaultReadObject();
checkType(getUnresolvedType());
int certNumber = in.readInt();
if (certNumber != 0) {
targetCerts = new Certificate[certNumber];
for (int i = 0; i < certNumber; i++) {
try {
String type = in.readUTF();
int length = in.readInt();
byte[] enc = new byte[length];
in.readFully(enc, 0, length);
targetCerts[i] = CertificateFactory.getInstance(type)
.generateCertificate(new ByteArrayInputStream(enc));
} catch (CertificateException cee) {
throw (IOException) new IOException("Error decoding certificate").initCause(cee);
}
}
}
}
}