diff --git a/service/pom.xml b/service/pom.xml index 30a7c6595..41d7f497f 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -57,6 +57,11 @@ signal-client-java 0.10.0 + + org.whispersystems + curve25519-java + 0.5.0 + io.dropwizard diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/auth/CertificateGenerator.java b/service/src/main/java/org/whispersystems/textsecuregcm/auth/CertificateGenerator.java index be372d30e..26b06b6b6 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/auth/CertificateGenerator.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/auth/CertificateGenerator.java @@ -7,8 +7,8 @@ package org.whispersystems.textsecuregcm.auth; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; -import org.whispersystems.libsignal.ecc.Curve; -import org.whispersystems.libsignal.ecc.ECPrivateKey; +import org.whispersystems.textsecuregcm.crypto.Curve; +import org.whispersystems.textsecuregcm.crypto.ECPrivateKey; import org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate; import org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate; import org.whispersystems.textsecuregcm.storage.Account; @@ -46,12 +46,7 @@ public class CertificateGenerator { } byte[] certificate = builder.build().toByteArray(); - byte[] signature; - try { - signature = Curve.calculateSignature(privateKey, certificate); - } catch (org.whispersystems.libsignal.InvalidKeyException e) { - throw new InvalidKeyException(e); - } + byte[] signature = Curve.calculateSignature(privateKey, certificate); return SenderCertificate.newBuilder() .setCertificate(ByteString.copyFrom(certificate)) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java index 3d5c58131..eddd1d9d0 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java @@ -8,8 +8,8 @@ package org.whispersystems.textsecuregcm.configuration; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.whispersystems.libsignal.ecc.Curve; -import org.whispersystems.libsignal.ecc.ECPrivateKey; +import org.whispersystems.textsecuregcm.crypto.Curve; +import org.whispersystems.textsecuregcm.crypto.ECPrivateKey; import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; import javax.validation.constraints.NotNull; diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/crypto/Curve.java b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/Curve.java new file mode 100644 index 000000000..65eb4257d --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/Curve.java @@ -0,0 +1,103 @@ +/* + * Copyright 2013-2020 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.crypto; + +import org.whispersystems.curve25519.Curve25519; +import org.whispersystems.curve25519.Curve25519KeyPair; + +import java.security.InvalidKeyException; + +import static org.whispersystems.curve25519.Curve25519.BEST; + +public class Curve { + + public static final int DJB_TYPE = 0x05; + + public static ECKeyPair generateKeyPair() { + Curve25519KeyPair keyPair = Curve25519.getInstance(BEST).generateKeyPair(); + + return new ECKeyPair(new DjbECPublicKey(keyPair.getPublicKey()), + new DjbECPrivateKey(keyPair.getPrivateKey())); + } + + public static ECPublicKey decodePoint(byte[] bytes, int offset) + throws InvalidKeyException + { + if (bytes == null || bytes.length - offset < 1) { + throw new InvalidKeyException("No key type identifier"); + } + + int type = bytes[offset] & 0xFF; + + switch (type) { + case Curve.DJB_TYPE: + if (bytes.length - offset < 33) { + throw new InvalidKeyException("Bad key length: " + bytes.length); + } + + byte[] keyBytes = new byte[32]; + System.arraycopy(bytes, offset+1, keyBytes, 0, keyBytes.length); + return new DjbECPublicKey(keyBytes); + default: + throw new InvalidKeyException("Bad key type: " + type); + } + } + + public static ECPrivateKey decodePrivatePoint(byte[] bytes) { + return new DjbECPrivateKey(bytes); + } + + public static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey) + throws InvalidKeyException + { + if (publicKey == null) { + throw new InvalidKeyException("public value is null"); + } + + if (privateKey == null) { + throw new InvalidKeyException("private value is null"); + } + + if (publicKey.getType() != privateKey.getType()) { + throw new InvalidKeyException("Public and private keys must be of the same type!"); + } + + if (publicKey.getType() == DJB_TYPE) { + return Curve25519.getInstance(BEST) + .calculateAgreement(((DjbECPublicKey) publicKey).getPublicKey(), + ((DjbECPrivateKey) privateKey).getPrivateKey()); + } else { + throw new InvalidKeyException("Unknown type: " + publicKey.getType()); + } + } + + public static byte[] calculateSignature(ECPrivateKey signingKey, byte[] message) + throws InvalidKeyException + { + if (signingKey == null || message == null) { + throw new InvalidKeyException("Values must not be null"); + } + + if (signingKey.getType() == DJB_TYPE) { + return Curve25519.getInstance(BEST) + .calculateSignature(((DjbECPrivateKey) signingKey).getPrivateKey(), message); + } else { + throw new InvalidKeyException("Unknown type: " + signingKey.getType()); + } + } + + public static boolean verifySignature(ECPublicKey signingKey, byte[] message, byte[] signature) + throws InvalidKeyException + { + if (signingKey.getType() == DJB_TYPE) { + return Curve25519.getInstance(BEST) + .verifySignature(((DjbECPublicKey) signingKey).getPublicKey(), message, signature); + } else { + throw new InvalidKeyException("Unknown type: " + signingKey.getType()); + } + } + +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPrivateKey.java b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPrivateKey.java new file mode 100644 index 000000000..47ffc84bb --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPrivateKey.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013-2020 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.crypto; + +public class DjbECPrivateKey implements ECPrivateKey { + + private final byte[] privateKey; + + DjbECPrivateKey(byte[] privateKey) { + this.privateKey = privateKey; + } + + @Override + public byte[] serialize() { + return privateKey; + } + + @Override + public int getType() { + return Curve.DJB_TYPE; + } + + public byte[] getPrivateKey() { + return privateKey; + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPublicKey.java b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPublicKey.java new file mode 100644 index 000000000..a24a96e35 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPublicKey.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013-2020 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.crypto; + +import org.whispersystems.textsecuregcm.util.ByteUtil; + +import java.math.BigInteger; +import java.util.Arrays; + +public class DjbECPublicKey implements ECPublicKey { + + private final byte[] publicKey; + + DjbECPublicKey(byte[] publicKey) { + this.publicKey = publicKey; + } + + @Override + public byte[] serialize() { + byte[] type = {Curve.DJB_TYPE}; + return ByteUtil.combine(type, publicKey); + } + + @Override + public int getType() { + return Curve.DJB_TYPE; + } + + @Override + public boolean equals(Object other) { + if (other == null) return false; + if (!(other instanceof DjbECPublicKey)) return false; + + DjbECPublicKey that = (DjbECPublicKey)other; + return Arrays.equals(this.publicKey, that.publicKey); + } + + @Override + public int hashCode() { + return Arrays.hashCode(publicKey); + } + + @Override + public int compareTo(ECPublicKey another) { + return new BigInteger(publicKey).compareTo(new BigInteger(((DjbECPublicKey)another).publicKey)); + } + + public byte[] getPublicKey() { + return publicKey; + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/crypto/ECKeyPair.java b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/ECKeyPair.java new file mode 100644 index 000000000..ad4120d22 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/ECKeyPair.java @@ -0,0 +1,25 @@ +/* + * Copyright 2013-2020 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.crypto; + +public class ECKeyPair { + + private final ECPublicKey publicKey; + private final ECPrivateKey privateKey; + + ECKeyPair(ECPublicKey publicKey, ECPrivateKey privateKey) { + this.publicKey = publicKey; + this.privateKey = privateKey; + } + + public ECPublicKey getPublicKey() { + return publicKey; + } + + public ECPrivateKey getPrivateKey() { + return privateKey; + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/crypto/ECPrivateKey.java b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/ECPrivateKey.java new file mode 100644 index 000000000..bac0d3234 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/ECPrivateKey.java @@ -0,0 +1,12 @@ +/* + * Copyright 2013-2020 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.crypto; + +public interface ECPrivateKey { + public byte[] serialize(); + public int getType(); +} + diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/crypto/ECPublicKey.java b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/ECPublicKey.java new file mode 100644 index 000000000..5511b0aaf --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/ECPublicKey.java @@ -0,0 +1,15 @@ +/* + * Copyright 2013-2020 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.crypto; + +public interface ECPublicKey extends Comparable { + + public static final int KEY_SIZE = 33; + + public byte[] serialize(); + + public int getType(); +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/workers/CertificateCommand.java b/service/src/main/java/org/whispersystems/textsecuregcm/workers/CertificateCommand.java index 118cf0a9a..f3e25ccd4 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/workers/CertificateCommand.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/workers/CertificateCommand.java @@ -10,9 +10,9 @@ import com.google.protobuf.ByteString; import net.sourceforge.argparse4j.impl.Arguments; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; -import org.whispersystems.libsignal.ecc.Curve; -import org.whispersystems.libsignal.ecc.ECKeyPair; -import org.whispersystems.libsignal.ecc.ECPrivateKey; +import org.whispersystems.textsecuregcm.crypto.Curve; +import org.whispersystems.textsecuregcm.crypto.ECKeyPair; +import org.whispersystems.textsecuregcm.crypto.ECPrivateKey; import org.whispersystems.textsecuregcm.entities.MessageProtos; import java.io.IOException; @@ -91,12 +91,7 @@ public class CertificateCommand extends Command { .build() .toByteArray(); - byte[] signature; - try { - signature = Curve.calculateSignature(key, certificate); - } catch (org.whispersystems.libsignal.InvalidKeyException e) { - throw new InvalidKeyException(e); - } + byte[] signature = Curve.calculateSignature(key, certificate); byte[] signedCertificate = MessageProtos.ServerCertificate.newBuilder() .setCertificate(ByteString.copyFrom(certificate)) diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/auth/CertificateGeneratorTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/auth/CertificateGeneratorTest.java index f5db90382..482ec7178 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/auth/CertificateGeneratorTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/auth/CertificateGeneratorTest.java @@ -6,7 +6,7 @@ package org.whispersystems.textsecuregcm.auth; import org.junit.Test; -import org.whispersystems.libsignal.ecc.Curve; +import org.whispersystems.textsecuregcm.crypto.Curve; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Device; diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java index c01c11f97..1a8026b0a 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java @@ -28,12 +28,12 @@ import org.signal.zkgroup.auth.AuthCredential; import org.signal.zkgroup.auth.AuthCredentialResponse; import org.signal.zkgroup.auth.ClientZkAuthOperations; import org.signal.zkgroup.auth.ServerZkAuthOperations; -import org.whispersystems.libsignal.ecc.Curve; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.CertificateGenerator; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.OptionalAccess; import org.whispersystems.textsecuregcm.controllers.CertificateController; +import org.whispersystems.textsecuregcm.crypto.Curve; import org.whispersystems.textsecuregcm.entities.DeliveryCertificate; import org.whispersystems.textsecuregcm.entities.GroupCredentials; import org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate;