package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.security.NoSuchAlgorithmException;
import com.nettgryppa.security.HashCash;
/**
* Extend Destination with methods to verify its Certificate.
* The router does not check Certificates, it doesn't care.
* Apps however (particularly addressbook) may wish to enforce various
* cert content, format, and policies.
* This class is written such that apps may extend it to
* create their own policies.
*
* @author zzz
*/
public class VerifiedDestination extends Destination {
public VerifiedDestination() {
super();
}
/**
* alternative constructor which takes a base64 string representation
* @param s a Base64 representation of the destination, as (eg) is used in hosts.txt
*/
public VerifiedDestination(String s) throws DataFormatException {
this();
fromBase64(s);
}
/**
* create from an existing Dest
* @param d must be non-null
*/
public VerifiedDestination(Destination d) throws DataFormatException {
this(d.toBase64());
}
/**
* verify the certificate.
* @param allowNone If true, allow a NULL or HIDDEN certificate.
*/
public boolean verifyCert(boolean allowNone) {
if (_publicKey == null || _signingKey == null || _certificate == null)
return false;
switch (_certificate.getCertificateType()) {
case Certificate.CERTIFICATE_TYPE_NULL:
case Certificate.CERTIFICATE_TYPE_HIDDEN:
return allowNone;
case Certificate.CERTIFICATE_TYPE_HASHCASH:
return verifyHashCashCert();
case Certificate.CERTIFICATE_TYPE_SIGNED:
return verifySignedCert();
}
return verifyUnknownCert();
}
/** Defaults for HashCash Certs */
public final static int MIN_HASHCASH_EFFORT = 20;
/**
* HashCash Certs are used to demonstrate proof-of-work.
*
* We define a HashCash Certificate as follows:
* - length: typically 47 bytes, but may vary somewhat
* - contents: A version 1 HashCash Stamp,
* defined at http://www.hashcash.org/docs/hashcash.html#stamp_format__version_1_
* modified to remove the contents of the 4th field (the resource)
* original is ver:bits:date:resource:[ext]:rand:counter
* I2P version is ver:bits:date::[ext]:rand:counter
* The HashCash is calculated with the following resource:
* The Base64 of the Public Key concatenated with the Base64 of the Signing Public Key
* (NOT the Base64 of the concatenated keys)
* To generate a Cert of this type, see PrivateKeyFile.main()
* To verify, we must put the keys back into the resource field of the stamp,
* then pass it to the HashCash constructor, then get the number of leading
* zeros and see if it meets our minimum effort.
*/
protected boolean verifyHashCashCert() {
String hcs = DataHelper.getUTF8(_certificate.getPayload());
int end1 = 0;
for (int i = 0; i < 3; i++) {
end1 = 1 + hcs.indexOf(':', end1);
if (end1 < 0)
return false;
}
int start2 = hcs.indexOf(':', end1);
if (start2 < 0)
return false;
// put the keys back into the 4th field of the stamp
hcs = hcs.substring(0, end1) + _publicKey.toBase64() + _signingKey.toBase64() + hcs.substring(start2);
HashCash hc;
try {
hc = new HashCash(hcs);
} catch (IllegalArgumentException iae) {
return false;
} catch (NoSuchAlgorithmException nsae) {
return false;
}
return hc.getValue() >= MIN_HASHCASH_EFFORT;
}
/** Defaults for Signed Certs */
public final static int CERTIFICATE_LENGTH_SIGNED = Signature.SIGNATURE_BYTES;
public final static int CERTIFICATE_LENGTH_SIGNED_WITH_HASH = Signature.SIGNATURE_BYTES + Hash.HASH_LENGTH;
/**
* Signed Certs are signed by a 3rd-party Destination.
* They can be used for a second-level domain, for example, to sign the
* Destination for a third-level domain. Or for a central authority
* to approve a destination.
*
* We define a Signed Certificate as follows:
* - length: Either 44 or 72 bytes
* - contents:
* 1: a 44 byte Signature
* 2 (optional): a 32 byte Hash of the signing Destination
* This can be a hint to the verification process to help find
* the identity and keys of the signing Destination.
* Data which is signed: The first 384 bytes of the Destination
* (i.e. the Public Key and Signing Public Key, WITHOUT the Certificate)
*
* It is not appropriate to enforce a particular delegation scheme here.
* The application will need to apply additional steps to select
* an appropriate signing Destination and verify the signature.
*
* See PrivateKeyFile.verifySignature() for sample verification code.
*
*/
protected boolean verifySignedCert() {
return _certificate.getPayload() != null &&
(_certificate.getPayload().length == CERTIFICATE_LENGTH_SIGNED ||
_certificate.getPayload().length == CERTIFICATE_LENGTH_SIGNED_WITH_HASH);
}
/**
* Reject all unknown certs
*/
protected boolean verifyUnknownCert() {
return false;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder(128);
buf.append(super.toString());
buf.append("\n\tVerified Certificate? ").append(verifyCert(true));
return buf.toString();
}
}