//
// This file is part of the prose package.
//
// The contents of this file are subject to the Mozilla Public License
// Version 1.1 (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.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
//
// The Original Code is prose.
//
// The Initial Developer of the Original Code is Andrei Popovici. Portions
// created by Andrei Popovici are Copyright (C) 2002 Andrei Popovici.
// All Rights Reserved.
//
// Contributor(s):
// $Id: SignedAspect.java,v 1.3 2008/11/18 11:44:47 anicoara Exp $
// =====================================================================
//
package ch.ethz.prose;
// used packages
import java.io.FileInputStream;
import java.io.IOException;
import java.rmi.MarshalledObject;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.SignedObject;
import java.util.List;
import ch.ethz.prose.crosscut.Crosscut;
import ch.ethz.inf.util.Logger;
/**
* Class SignedAspect is a wrapper for an Aspect contained in a
* SignedObject. Thus it is possible to verify the signer of the
* extension before it is used. For security reason, this class
* is (and has to be) declared <code>final</code><p>
*
* At construction, the public and private keys passed as <code>
* KeyPair</code> are not checked for being a valid pair
* and the private key is not stored for later use. Only
* an explicit call to <code>verifyExtension</code> does
* check the public key stored. The signing algorithm
* to use is deduced by the algorithm type of the public
* key: if the public key is of type "DSA" then the algorithm
* "SHA1withDSA" is used, in case of an "RSA" key it is "MD5withRSA".
* Keys generated with the <code>keytool</code> for the standard
* Java 2 keystore are of type "DSA" if not specified otherwise.
* The creation of certificates has to be performed by a third party
* tool, such as the ones provided with OpenSSL.<p>
*
* The JCE signing engine that is used can not be specified for the
* moment, this class simply uses the first available one.<p>
*
* To repeat: It is up to the user of this class to assign trust to such
* an extension, this class can only be used to transfer an extension
* secured from unnoticed changes.<p>
*
* Remark: to simplify the security auditing of this class, the
* necessary and constant parameters are passed to the constructor
* and no additional setter-methods are available.
*
* @version $Revision: 1.3 $
* @author Marcel Muller
*/
final public class SignedAspect extends Aspect {
private static final long serialVersionUID = 3256441395777779248L;
private static KeyPair keyPair;
private SignedObject signedExtension;
private PublicKey publicKey;
// private Certificate[] certificateChain; // will work only with jdk 1.3 and later
transient Aspect wrappedExtension;
transient boolean verified = false;
/**
* Constructs a signed extension and protects it from being modified unnoticed.
* Only the public key from the key pair passed is store in the generated object.
*
* @param extension the extension to be wrapped into a SignedObject
* @param keys the key pair used to sign and verify the extension
*/
public SignedAspect(Aspect extension, KeyPair keys) {
publicKey = keys.getPublic();
try {
Signature signingEngine = Signature.getInstance(getSigningAlgorithm());
signedExtension = new SignedObject(new MarshalledObject(extension), keys.getPrivate(), signingEngine);
} catch (Exception e) {
throw new IllegalArgumentException("failed to sign extension ("+e+")");
}
}
// delegate work
public void insertionAction(boolean beforeInsertion) throws AspectInsertionException {
getExtension().insertionAction(beforeInsertion);
}
public void withdrawalAction(boolean beforeWithdrawal) {
getExtension().withdrawalAction(beforeWithdrawal);
}
protected Crosscut[] crosscuts() {
return getExtension().crosscuts();
}
public List getCrosscuts() {
return getExtension().getCrosscuts();
}
/**
* Convinience method to sign extensions. Throws IllegalStateException
* if signing is not possible.<p>
*
* FIX: CONTAINS A LOT OF HARD CODED STUFF!!!!
*/
public static SignedAspect signExtension(Aspect ext) {
try {
if (keyPair == null) {
String ksl = System.getProperty("ch.ethz.prose.keystore.location");
String ksp = System.getProperty("ch.ethz.prose.keystore.password");
String alias = "runes-system"; // FIX
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(ksl), ksp.toCharArray());
PrivateKey privateKey = (PrivateKey) ks.getKey(alias, alias.toCharArray()); // FIX, password
PublicKey publicKey = ks.getCertificate(alias).getPublicKey();
keyPair = new KeyPair(publicKey, privateKey);
}
return new SignedAspect(ext, keyPair);
} catch (Exception e) {
Logger.error("SignedAspect.signExtension: could not sign extension", e);
throw new IllegalStateException("could not sign extension");
}
}
/**
* Verifies that the public key stored in this object corresponds
* to the private key used to sign the extension.
*
* @throws NoSuchAlgorithmException
* @throws SignatureException
* @throws InvalidKeyException
*/
public void verifyExtension() throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
Signature verificationEngine = Signature.getInstance(getSigningAlgorithm());
signedExtension.verify(publicKey, verificationEngine);
verified = true;
}
/**
* Returns wrapped extension without checking it for being signed
* by a key corresponding to the public key stored.
*
* @return the wrapped extension
*/
public Aspect getExtension() {
try {
if (wrappedExtension == null) {
wrappedExtension = (Aspect) ((MarshalledObject) signedExtension.getObject()).get();
}
return wrappedExtension;
} catch (IOException e) {
Logger.error("SignedAspect.getExtension: io exception", e);
throw new RuntimeException("contained exception " + e);
} catch (ClassNotFoundException e) {
Logger.error("SignedAspect.getExtension: class not found", e);
throw new RuntimeException("contained exception " + e);
}
}
/**
* Returns public key that possibly corresponds to signing key
*
* @return the public key passed in the constructor
*/
public PublicKey getPublicKey() {
return publicKey;
}
/**
* Returns signing algorithm name
*
* @return the algorithm name
*/
public String getSigningAlgorithm() {
String signingAlgorithm;
if (publicKey.getAlgorithm().equals("DSA")) {
signingAlgorithm = "SHA1withDSA";
} else if (publicKey.getAlgorithm().equals("RSA")) {
signingAlgorithm = "MD5withRSA";
} else {
throw new IllegalArgumentException("unknown key algorithm, currently supported are DSA and RSA");
}
return signingAlgorithm;
}
/**
* Implementation: we don't use getExtension() here, as we don't want to
* de-marshall extension just because of the toString call
*/
public String toString() {
if (wrappedExtension != null) {
return "SignedAspect, "+ (verified?"verified":"not verified") +", containing " + wrappedExtension.toString();
} else {
return "SignedAspect, not accessed";
}
}
//public void setCertificateChain(Certificate[] certificateChain) {
// this.certificateChain = certificateChain;
//}
//public Certificate[] getCertificateChain() {
// return certificateChain;
//}
/**
* Delegate to wrapped extension
*/
public boolean equals(Object o) {
if (o instanceof SignedAspect) {
return getExtension().equals(((SignedAspect) o).getExtension());
} else if (o instanceof Aspect) {
return getExtension().equals(o);
} else {
return false;
}
}
/**
* Delegate to wrapped extension
*/
public int hashCode() {
return getExtension().hashCode();
}
}