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.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; import net.i2p.crypto.SHA256Generator; import net.i2p.crypto.SigType; /** * KeysAndCert has a public key, a signing key, and a certificate. * In that order. * We also store a cached Hash. * * Implemented in 0.8.2 and retrofitted over Destination and RouterIdentity. * There's actually no difference between the two of them. * * As of 0.9.9 this data structure is immutable after the two keys and the certificate * are set; attempts to change them will throw an IllegalStateException. * * @since 0.8.2 * @author zzz */ public class KeysAndCert extends DataStructureImpl { protected PublicKey _publicKey; protected SigningPublicKey _signingKey; protected Certificate _certificate; private Hash __calculatedHash; protected byte[] _padding; public Certificate getCertificate() { return _certificate; } /** * @throws IllegalStateException if was already set */ public void setCertificate(Certificate cert) { if (_certificate != null) throw new IllegalStateException(); _certificate = cert; } /** * @return null if not set or unknown * @since 0.9.17 */ public SigType getSigType() { if (_certificate == null) return null; if (_certificate.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) { try { KeyCertificate kcert = _certificate.toKeyCertificate(); return kcert.getSigType(); } catch (DataFormatException dfe) {} } return SigType.DSA_SHA1; } public PublicKey getPublicKey() { return _publicKey; } /** * @throws IllegalStateException if was already set */ public void setPublicKey(PublicKey key) { if (_publicKey != null) throw new IllegalStateException(); _publicKey = key; } public SigningPublicKey getSigningPublicKey() { return _signingKey; } /** * @throws IllegalStateException if was already set */ public void setSigningPublicKey(SigningPublicKey key) { if (_signingKey != null) throw new IllegalStateException(); _signingKey = key; } /** * @since 0.9.16 */ public byte[] getPadding() { return _padding; } /** * @throws IllegalStateException if was already set * @since 0.9.12 */ public void setPadding(byte[] padding) { if (_padding != null) throw new IllegalStateException(); _padding = padding; } /** * @throws IllegalStateException if data already set */ public void readBytes(InputStream in) throws DataFormatException, IOException { if (_publicKey != null || _signingKey != null || _certificate != null) throw new IllegalStateException(); _publicKey = PublicKey.create(in); SigningPublicKey spk = SigningPublicKey.create(in); Certificate cert = Certificate.create(in); if (cert.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) { // convert SPK to new SPK and padding KeyCertificate kcert = cert.toKeyCertificate(); _signingKey = spk.toTypedKey(kcert); _padding = spk.getPadding(kcert); _certificate = kcert; } else { _signingKey = spk; _certificate = cert; } } public void writeBytes(OutputStream out) throws DataFormatException, IOException { if ((_certificate == null) || (_publicKey == null) || (_signingKey == null)) throw new DataFormatException("Not enough data to format the router identity"); _publicKey.writeBytes(out); if (_padding != null) out.write(_padding); else if (_signingKey.length() < SigningPublicKey.KEYSIZE_BYTES) throw new DataFormatException("No padding set"); _signingKey.writeTruncatedBytes(out); _certificate.writeBytes(out); } @Override public boolean equals(Object object) { if (object == this) return true; if ((object == null) || !(object instanceof KeysAndCert)) return false; KeysAndCert ident = (KeysAndCert) object; return DataHelper.eq(_signingKey, ident._signingKey) && DataHelper.eq(_publicKey, ident._publicKey) && Arrays.equals(_padding, ident._padding) && DataHelper.eq(_certificate, ident._certificate); } /** the signing key has enough randomness in it to use it by itself for speed */ @Override public int hashCode() { // don't use public key, some app devs thinking of using // an all-zeros or leading-zeros public key for destinations if (_signingKey == null) return 0; return _signingKey.hashCode(); } @Override public String toString() { StringBuilder buf = new StringBuilder(256); buf.append('[').append(getClass().getSimpleName()).append(": "); buf.append("\n\tHash: ").append(getHash().toBase64()); buf.append("\n\tCertificate: ").append(_certificate); buf.append("\n\tPublicKey: ").append(_publicKey); buf.append("\n\tSigningPublicKey: ").append(_signingKey); if (_padding != null) buf.append("\n\tPadding: ").append(_padding.length).append(" bytes"); buf.append(']'); return buf.toString(); } /** * Throws IllegalStateException if keys and cert are not initialized, * as of 0.9.12. Prior to that, returned null. * * @throws IllegalStateException */ @Override public Hash calculateHash() { return getHash(); } /** * Throws IllegalStateException if keys and cert are not initialized, * as of 0.9.12. Prior to that, returned null. * * @throws IllegalStateException */ public Hash getHash() { if (__calculatedHash != null) return __calculatedHash; byte identBytes[]; try { ByteArrayOutputStream baos = new ByteArrayOutputStream(400); writeBytes(baos); identBytes = baos.toByteArray(); } catch (IOException ioe) { throw new IllegalStateException("KAC hash error", ioe); } catch (DataFormatException dfe) { throw new IllegalStateException("KAC hash error", dfe); } __calculatedHash = SHA256Generator.getInstance().calculateHash(identBytes); return __calculatedHash; } }