[de]serialization of profile headers and profiles

dbus-notify
Zlatin Balevsky 2022-05-30 16:55:02 +01:00
parent d3dcbba34c
commit ece040be76
No known key found for this signature in database
GPG Key ID: A72832072D525E41
6 changed files with 263 additions and 4 deletions

View File

@ -6,6 +6,14 @@ public class Constants {
public static final byte PERSONA_VERSION = (byte)1;
public static final String INVALID_NICKNAME_CHARS = "'\"();<>=@$%";
public static final int MAX_NICKNAME_LENGTH = 30;
public static final byte PROFILE_HEADER_VERSION = (byte)1;
public static final int MAX_PROFILE_TITLE_LENGTH = 128;
public static final byte PROFILE_VERSION = (byte)1;
public static final int MAX_PROFILE_IMAGE_LENGTH = 200 * 1024;
public static final int MAX_PROFILE_LENGTH = 0x1 << 18;
public static final byte FILE_CERT_VERSION = (byte)2;
public static final int CHAT_VERSION = 2;
@ -23,7 +31,6 @@ public class Constants {
public static final long MAX_HEADER_TIME = 60 * 1000;
public static final int MAX_RESULTS = 0x1 << 20;
public static final int MAX_PROFILE_LENGTH = 0x1 << 18;
public static final int MAX_COMMENT_LENGTH = 0x1 << 15;

View File

@ -1,6 +1,6 @@
package com.muwire.core;
class InvalidSignatureException extends Exception {
public class InvalidSignatureException extends Exception {
public InvalidSignatureException(String message, Throwable cause) {
super(message, cause);

View File

@ -13,11 +13,11 @@ import java.nio.charset.StandardCharsets;
public class Name {
final String name;
Name(String name) {
public Name(String name) {
this.name = name;
}
Name(InputStream nameStream) throws IOException {
public Name(InputStream nameStream) throws IOException {
DataInputStream dis = new DataInputStream(nameStream);
int length = dis.readUnsignedShort();
byte [] nameBytes = new byte[length];

View File

@ -0,0 +1,135 @@
package com.muwire.core.profile;
import com.muwire.core.Constants;
import com.muwire.core.InvalidNicknameException;
import com.muwire.core.InvalidSignatureException;
import com.muwire.core.Name;
import net.i2p.crypto.DSAEngine;
import net.i2p.data.*;
import java.io.*;
public class MWProfile {
private final byte version;
private final MWProfileHeader header;
private final byte[] image;
private final MWProfileImageFormat format;
private final Name body;
private final byte [] sig;
private volatile byte[] payload;
private volatile String base64;
public MWProfile(InputStream inputStream) throws IOException, DataFormatException,
InvalidSignatureException, InvalidNicknameException {
version = (byte) (inputStream.read() & 0xFF);
if (version != Constants.PROFILE_VERSION)
throw new IOException("unknown version " + version);
header = new MWProfileHeader(inputStream);
DataInputStream dais = new DataInputStream(inputStream);
byte imageFormat = dais.readByte();
if (imageFormat == 0)
format = MWProfileImageFormat.PNG;
else if (imageFormat == 1)
format = MWProfileImageFormat.JPG;
else
throw new IOException("unknown image format for " + header.getPersona().getHumanReadableName() + " " + imageFormat);
int imageLength = dais.readInt();
if (imageLength > Constants.MAX_PROFILE_IMAGE_LENGTH)
throw new IOException("image too long for " + header.getPersona().getHumanReadableName() + " " + imageLength);
image = new byte[imageLength];
dais.readFully(image);
body = new Name(dais);
if (body.getName().length() > Constants.MAX_COMMENT_LENGTH)
throw new IOException("body too long for " + header.getPersona().getHumanReadableName() + " " + body.getName().length());
sig = new byte[Constants.SIG_TYPE.getSigLen()];
dais.readFully(sig);
if (!verify())
throw new InvalidSignatureException("Profile for " + header.getPersona().getHumanReadableName() + " did not verify");
}
public MWProfile(MWProfileHeader header, byte[] image, MWProfileImageFormat format, String body, SigningPrivateKey spk)
throws IOException, DataFormatException {
this.version = Constants.PROFILE_VERSION;
this.header = header;
this.format = format;
this.image = image;
this.body = new Name(body);
byte [] signablePayload = signablePayload();
Signature signature = DSAEngine.getInstance().sign(signablePayload, spk);
this.sig = signature.getData();
}
private byte[] signablePayload() throws IOException, DataFormatException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream daos = new DataOutputStream(baos);
daos.write(version);
header.write(daos);
daos.write((byte) format.ordinal());
daos.writeInt(image.length);
daos.write(image);
body.write(daos);
daos.close();
return baos.toByteArray();
}
private boolean verify() throws IOException, DataFormatException {
byte [] payload = signablePayload();
SigningPublicKey spk = header.getPersona().getDestination().getSigningPublicKey();
Signature signature = new Signature(spk.getType(), sig);
return DSAEngine.getInstance().verifySignature(signature, payload, spk);
}
public void write(OutputStream outputStream) throws IOException, DataFormatException {
if (payload == null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(signablePayload());
baos.write(sig);
payload = baos.toByteArray();
}
outputStream.write(payload);
}
public String toBase64() {
if (base64 == null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
write(baos);
} catch (Exception impossible) {
throw new RuntimeException(impossible);
}
base64 = Base64.encode(baos.toByteArray());
}
return base64;
}
public MWProfileHeader getHeader() {
return header;
}
public MWProfileImageFormat getFormat() {
return format;
}
public byte[] getImage() {
return image;
}
public String getBody() {
return body.getName();
}
}

View File

@ -0,0 +1,112 @@
package com.muwire.core.profile;
import com.muwire.core.*;
import net.i2p.crypto.DSAEngine;
import net.i2p.data.*;
import java.io.*;
public class MWProfileHeader {
private final byte version;
private final Persona persona;
private final byte[] thumbNail;
private final Name title;
private final byte[] sig;
private volatile String base64;
private volatile byte[] payload;
public MWProfileHeader(InputStream inputStream) throws IOException, DataFormatException,
InvalidSignatureException, InvalidNicknameException {
version = (byte) (inputStream.read() & 0xFF);
if (version != Constants.PROFILE_HEADER_VERSION)
throw new IOException("unknown version " + version);
persona = new Persona(inputStream);
DataInputStream dis = new DataInputStream(inputStream);
int thumbnailLength = dis.readUnsignedShort();
thumbNail = new byte[thumbnailLength];
dis.readFully(thumbNail);
title = new Name(dis);
if (title.getName().length() > Constants.MAX_PROFILE_TITLE_LENGTH)
throw new IOException("Profile title too long " + title.getName().length());
sig = new byte[Constants.SIG_TYPE.getSigLen()];
dis.readFully(sig);
if (!verify())
throw new InvalidSignatureException("Profile header for " + persona.getHumanReadableName() + " did not verify");
}
public MWProfileHeader(Persona persona, byte [] thumbNail, String title, SigningPrivateKey spk)
throws IOException, DataFormatException {
this.version = Constants.PROFILE_HEADER_VERSION;
this.persona = persona;
this.thumbNail = thumbNail;
this.title = new Name(title);
byte [] signablePayload = signablePayload();
Signature signature = DSAEngine.getInstance().sign(signablePayload, spk);
this.sig = signature.getData();
}
private byte[] signablePayload() throws IOException, DataFormatException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream daos = new DataOutputStream(baos);
daos.write(version);
persona.write(daos);
daos.writeShort((short) thumbNail.length);
daos.write(thumbNail);
title.write(daos);
daos.close();
return baos.toByteArray();
}
private boolean verify() throws IOException, DataFormatException {
byte [] payload = signablePayload();
SigningPublicKey spk = persona.getDestination().getSigningPublicKey();
Signature signature = new Signature(spk.getType(), sig);
return DSAEngine.getInstance().verifySignature(signature, payload, spk);
}
public void write(OutputStream outputStream) throws IOException, DataFormatException {
if (payload == null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream daos = new DataOutputStream(baos);
daos.write(signablePayload());
daos.write(sig);
daos.close();
payload = baos.toByteArray();
}
outputStream.write(payload);
}
public String toBase64() {
if (base64 == null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
write(baos);
} catch (Exception impossible) {
throw new RuntimeException(impossible);
}
base64 = Base64.encode(baos.toByteArray());
}
return base64;
}
public Persona getPersona() {
return persona;
}
public byte[] getThumbNail() {
return thumbNail;
}
public String getTitle() {
return title.getName();
}
}

View File

@ -0,0 +1,5 @@
package com.muwire.core.profile;
public enum MWProfileImageFormat {
PNG, JPG
}