package net.i2p.data; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import net.i2p.crypto.SigType; /** * This certificate type gets its own class because it's going to be used a lot. * * The crypto type is assumed to be always 0x0000 (ElG) for now. * * @since 0.9.12 */ public class KeyCertificate extends Certificate { public static final int HEADER_LENGTH = 4; /** @since 0.9.22 pkg private for Certificate.create() */ static final byte[] Ed25519_PAYLOAD = new byte[] { 0, (byte) (SigType.EdDSA_SHA512_Ed25519.getCode()), 0, 0 }; /** @since 0.9.22 pkg private for Certificate.create() */ static final byte[] ECDSA256_PAYLOAD = new byte[] { 0, (byte) (SigType.ECDSA_SHA256_P256.getCode()), 0, 0 }; /** * An immutable ElG/ECDSA-P256 certificate. */ public static final KeyCertificate ELG_ECDSA256_CERT; /** * An immutable ElG/Ed25519 certificate. * @since 0.9.22 */ public static final KeyCertificate ELG_Ed25519_CERT; static { KeyCertificate kc; try { kc = new ECDSA256Cert(); } catch (DataFormatException dfe) { throw new RuntimeException(dfe); // won't happen } ELG_ECDSA256_CERT = kc; try { kc = new Ed25519Cert(); } catch (DataFormatException dfe) { throw new RuntimeException(dfe); // won't happen } ELG_Ed25519_CERT = kc; } /** * @param payload 4 bytes minimum if non-null * @throws DataFormatException */ public KeyCertificate(byte[] payload) throws DataFormatException { super(CERTIFICATE_TYPE_KEY, payload); if (payload != null && payload.length < HEADER_LENGTH) throw new DataFormatException("data"); } /** * A KeyCertificate with crypto type 0 (ElGamal) * and the signature type and extra data from the given public key. * * @param spk non-null data non-null * @throws IllegalArgumentException */ public KeyCertificate(SigningPublicKey spk) { super(CERTIFICATE_TYPE_KEY, null); if (spk == null || spk.getData() == null) throw new IllegalArgumentException(); SigType type = spk.getType(); int len = type.getPubkeyLen(); int extra = Math.max(0, len - 128); _payload = new byte[HEADER_LENGTH + extra]; int code = type.getCode(); _payload[0] = (byte) (code >> 8); _payload[1] = (byte) (code & 0xff); // 2 and 3 always 0, it is the only crypto code for now if (extra > 0) System.arraycopy(spk.getData(), 128, _payload, HEADER_LENGTH, extra); } /** * A KeyCertificate with crypto type 0 (ElGamal) * and the signature type as specified. * Payload is created. * If type.getPubkeyLen() is greater than 128, caller MUST * fill in the extra key data in the payload. * * @param type non-null * @throws IllegalArgumentException */ public KeyCertificate(SigType type) { super(CERTIFICATE_TYPE_KEY, null); int len = type.getPubkeyLen(); int extra = Math.max(0, len - 128); _payload = new byte[HEADER_LENGTH + extra]; int code = type.getCode(); _payload[0] = (byte) (code >> 8); _payload[1] = (byte) (code & 0xff); // 2 and 3 always 0, it is the only crypto code for now } /** * Up-convert a cert to this class * * @param cert payload 4 bytes minimum if non-null * @throws DataFormatException if cert type != CERTIFICATE_TYPE_KEY */ public KeyCertificate(Certificate cert) throws DataFormatException { this(cert.getPayload()); if (cert.getCertificateType() != CERTIFICATE_TYPE_KEY) throw new DataFormatException("type"); } /** * @return -1 if unset */ public int getSigTypeCode() { if (_payload == null) return -1; return ((_payload[0] & 0xff) << 8) | (_payload[1] & 0xff); } /** * @return -1 if unset */ public int getCryptoTypeCode() { if (_payload == null) return -1; return ((_payload[2] & 0xff) << 8) | (_payload[3] & 0xff); } /** * @return null if unset or unknown */ public SigType getSigType() { return SigType.getByCode(getSigTypeCode()); } /** * Signing Key extra data, if any, is first in the array. * Crypto Key extra data, if any, is second in the array, * at offset max(0, getSigType().getPubkeyLen() - 128) * * @return null if unset or none */ public byte[] getExtraKeyData() { if (_payload == null || _payload.length <= HEADER_LENGTH) return null; byte[] rv = new byte[_payload.length - HEADER_LENGTH]; System.arraycopy(_payload, HEADER_LENGTH, rv, 0, rv.length); return rv; } /** * Signing Key extra data, if any. * * @return null if unset or none * @throws UnsupportedOperationException if the sig type is unsupported */ public byte[] getExtraSigningKeyData() { // we assume no crypto key data if (_payload == null || _payload.length <= HEADER_LENGTH) return null; SigType type = getSigType(); if (type == null) throw new UnsupportedOperationException("unknown sig type"); int extra = Math.max(0, type.getPubkeyLen() - 128); if (_payload.length == HEADER_LENGTH + extra) return getExtraKeyData(); byte[] rv = new byte[extra]; System.arraycopy(_payload, HEADER_LENGTH, rv, 0, extra); return rv; } // todo // constructor w/ crypto type // getCryptoType() // getCryptoDataOffset() @Override public KeyCertificate toKeyCertificate() { return this; } @Override public String toString() { StringBuilder buf = new StringBuilder(64); buf.append("[Certificate: type: Key certificate"); if (_payload == null) { buf.append(" null payload"); } else { buf.append("\n\tCrypto type: ").append(getCryptoTypeCode()); buf.append("\n\tSig type: ").append(getSigTypeCode()) .append(" (").append(getSigType()).append(')'); if (_payload.length > HEADER_LENGTH) buf.append("\n\tKey data: ").append(_payload.length - HEADER_LENGTH).append(" bytes"); } buf.append("]"); return buf.toString(); } /** * An immutable ElG/ECDSA-256 certificate. */ private static final class ECDSA256Cert extends KeyCertificate { private static final byte[] ECDSA256_DATA = new byte[] { CERTIFICATE_TYPE_KEY, 0, HEADER_LENGTH, 0, (byte) (SigType.ECDSA_SHA256_P256.getCode()), 0, 0 }; private static final int ECDSA256_LENGTH = ECDSA256_DATA.length; private final int _hashcode; public ECDSA256Cert() throws DataFormatException { super(ECDSA256_PAYLOAD); _hashcode = super.hashCode(); } /** @throws RuntimeException always */ @Override public void setCertificateType(int type) { throw new RuntimeException("Data already set"); } /** @throws RuntimeException always */ @Override public void setPayload(byte[] payload) { throw new RuntimeException("Data already set"); } /** @throws RuntimeException always */ @Override public void readBytes(InputStream in) throws DataFormatException, IOException { throw new RuntimeException("Data already set"); } /** Overridden for efficiency */ @Override public void writeBytes(OutputStream out) throws IOException { out.write(ECDSA256_DATA); } /** Overridden for efficiency */ @Override public int writeBytes(byte target[], int offset) { System.arraycopy(ECDSA256_DATA, 0, target, offset, ECDSA256_LENGTH); return ECDSA256_LENGTH; } /** @throws RuntimeException always */ @Override public int readBytes(byte source[], int offset) throws DataFormatException { throw new RuntimeException("Data already set"); } /** Overridden for efficiency */ @Override public int size() { return ECDSA256_LENGTH; } /** Overridden for efficiency */ @Override public int hashCode() { return _hashcode; } } /** * An immutable ElG/Ed25519 certificate. * @since 0.9.22 */ private static final class Ed25519Cert extends KeyCertificate { private static final byte[] ED_DATA = new byte[] { CERTIFICATE_TYPE_KEY, 0, HEADER_LENGTH, 0, (byte) SigType.EdDSA_SHA512_Ed25519.getCode(), 0, 0 }; private static final int ED_LENGTH = ED_DATA.length; private final int _hashcode; public Ed25519Cert() throws DataFormatException { super(Ed25519_PAYLOAD); _hashcode = super.hashCode(); } /** @throws RuntimeException always */ @Override public void setCertificateType(int type) { throw new RuntimeException("Data already set"); } /** @throws RuntimeException always */ @Override public void setPayload(byte[] payload) { throw new RuntimeException("Data already set"); } /** @throws RuntimeException always */ @Override public void readBytes(InputStream in) throws DataFormatException, IOException { throw new RuntimeException("Data already set"); } /** Overridden for efficiency */ @Override public void writeBytes(OutputStream out) throws IOException { out.write(ED_DATA); } /** Overridden for efficiency */ @Override public int writeBytes(byte target[], int offset) { System.arraycopy(ED_DATA, 0, target, offset, ED_LENGTH); return ED_LENGTH; } /** @throws RuntimeException always */ @Override public int readBytes(byte source[], int offset) throws DataFormatException { throw new RuntimeException("Data already set"); } /** Overridden for efficiency */ @Override public int size() { return ED_LENGTH; } /** Overridden for efficiency */ @Override public int hashCode() { return _hashcode; } } }