Skip to content

Commit

Permalink
v3.4 (build 257)
Browse files Browse the repository at this point in the history
  • Loading branch information
finiasz committed Nov 28, 2024
1 parent 2cd8881 commit 903ccd8
Show file tree
Hide file tree
Showing 315 changed files with 20,759 additions and 14,536 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ avd/
build
/captures
.externalNativeBuild
/obv_messenger/.kotlin/
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# Build 257 (3.4)
2024-11-28

- Olvid now targets Android 15 (API 35)
- Improved global search to also match link title/description
- Allow contact introduction directly from the discussion screen
- [optimization] attachment download/upload progresses are no longer written to database
- several small bug fixes

# Build 256 (3.3.1)
2024-11-16

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ public static UID post(ChannelManagerSession channelManagerSession, ChannelMessa


EncryptedBytes encryptedContent = authEnc.encrypt(messageKey, paddedPlaintext, prng);
messageToSend = new MessageToSend(message.getSendChannelInfo().getFromIdentity(), messageUid, server, encryptedContent, headers);
messageToSend = new MessageToSend(message.getSendChannelInfo().getFromIdentity(), messageUid, server, encryptedContent, headers, channelProtocolMessageToSend.hasUserContent());
break;
}
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import java.nio.charset.StandardCharsets;

public abstract class Constants {
public static final int CURRENT_ENGINE_DB_SCHEMA_VERSION = 41;
public static final int CURRENT_ENGINE_DB_SCHEMA_VERSION = 42;
public static final int SERVER_API_VERSION = 18;
public static final int CURRENT_BACKUP_JSON_VERSION = 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,8 @@ public void retryScheduledRunnables() {
}

// for polling only
// public ScheduledFuture<?> scheduleAtFixedRate(Runnable runnable, int i, long delay, TimeUnit timeUnit) {
// return scheduler.scheduleAtFixedRate(runnable, i, delay, timeUnit);
// }

protected long computeReschedulingDelay(int failedAttemptCount) {
return (long) ((Constants.BASE_RESCHEDULING_TIME << failedAttemptCount) * (1 + random.nextFloat()));
long base = Constants.BASE_RESCHEDULING_TIME << Math.min(failedAttemptCount, 32);
return (long) (base * (1 + random.nextFloat()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ public class ChannelProtocolMessageToSend implements ChannelMessageToSend {
private final SendChannelInfo sendChannelInfo;
private final Encoded encodedElements;
private final boolean partOfFullRatchetProtocolOfTheSendSeed;
private final boolean hasUserContent;

public ChannelProtocolMessageToSend(SendChannelInfo sendChannelInfo, Encoded encodedElements, boolean partOfFullRatchetProtocolOfTheSendSeed) {
public ChannelProtocolMessageToSend(SendChannelInfo sendChannelInfo, Encoded encodedElements, boolean partOfFullRatchetProtocolOfTheSendSeed, boolean hasUserContent) {
this.sendChannelInfo = sendChannelInfo;
this.encodedElements = encodedElements;
this.partOfFullRatchetProtocolOfTheSendSeed = partOfFullRatchetProtocolOfTheSendSeed;
this.hasUserContent = hasUserContent;
}

@Override
Expand All @@ -50,4 +52,8 @@ public Encoded getEncodedElements() {
public boolean isPartOfFullRatchetProtocolOfTheSendSeed() {
return partOfFullRatchetProtocolOfTheSendSeed;
}

public boolean hasUserContent() {
return hasUserContent;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ public class MessageToSend {
private final boolean isApplicationMessage;
private final boolean isVoipMessage;

public MessageToSend(Identity ownedIdentity, UID uid, String server, EncryptedBytes encryptedContent, Header[] headers) {
this(ownedIdentity, uid, server, encryptedContent, null, headers, new Attachment[0], false, false);
public MessageToSend(Identity ownedIdentity, UID uid, String server, EncryptedBytes encryptedContent, Header[] headers, boolean hasUserContent) {
this(ownedIdentity, uid, server, encryptedContent, null, headers, new Attachment[0], hasUserContent, false);
}

public MessageToSend(Identity ownedIdentity, UID uid, String server, EncryptedBytes encryptedContent, EncryptedBytes encryptedExtendedContent, Header[] headers, Attachment[] attachments, boolean isApplicationMessage, boolean isVoipMessage) {
Expand Down
11 changes: 11 additions & 0 deletions obv_engine/engine/src/main/java/io/olvid/engine/engine/Engine.java
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,17 @@ public RegisterApiKeyResult registerOwnedIdentityApiKeyOnServer(byte[] bytesOwne
}
}

@Override
public void updateKeycloakTransferRestrictedIfNeeded(byte[] bytesOwnedIdentity, String serverUrl, boolean transferRestricted) {
try (EngineSession engineSession = getSession()) {
Identity ownedIdentity = Identity.of(bytesOwnedIdentity);
identityManager.updateKeycloakTransferRestrictedIfNeeded(engineSession.session, ownedIdentity, serverUrl, transferRestricted);
engineSession.session.commit();
} catch (Exception e) {
Logger.x(e);
}
}

@Override
public void updateKeycloakPushTopicsIfNeeded(byte[] bytesOwnedIdentity, String serverUrl, List<String> pushTopics) {
try (EngineSession engineSession = getSession()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ enum ListenerPriority {
void setOwnedIdentityKeycloakSignatureKey(byte[] bytesOwnedIdentity, JsonWebKey signatureKey) throws Exception;
ObvIdentity bindOwnedIdentityToKeycloak(byte[] bytesOwnedIdentity, ObvKeycloakState keycloakState, String keycloakUserId);
void unbindOwnedIdentityFromKeycloak(byte[] bytesOwnedIdentity);
void updateKeycloakTransferRestrictedIfNeeded(byte[] bytesOwnedIdentity, String serverUrl, boolean transferRestricted);
void updateKeycloakPushTopicsIfNeeded(byte[] bytesOwnedIdentity, String serverUrl, List<String> pushTopics);
void updateKeycloakRevocationList(byte[] bytesOwnedIdentity, long latestRevocationListTimestamp, List<String> signedRevocations);
void setOwnedIdentityKeycloakSelfRevocationTestNonce(byte[] bytesOwnedIdentity, String serverUrl, String nonce);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ private static String nullOrTrim(String in) {
return null;
}
String out = in.trim();
if (out.length() == 0) {
if (out.isEmpty()) {
return null;
}
return out;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,17 @@ public void setTransferSasAndDeviceUid(String sas, byte[] deviceUidToKeepActive)
}
}

public void setTransferAuthenticationProof(String signature, String serializedAuthState) throws Exception {
if (this.category.id == Category.TRANSFER_DIALOG_CATEGORY && this.category.obvTransferStep.getStep() == ObvTransferStep.Step.TARGET_REQUESTS_KEYCLOAK_AUTHENTICATION_PROOF) {
encodedResponse = Encoded.of(new Encoded[]{
Encoded.of(signature),
Encoded.of(serializedAuthState),
});
} else {
throw new Exception();
}
}

// endregion


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ public static ObvTransferStep of(Encoded encoded) throws DecodingException {
return new SourceSnapshotSent(encodedParts);
case TARGET_SNAPSHOT_RECEIVED:
return new TargetSnapshotReceived(encodedParts);
case TARGET_REQUESTS_KEYCLOAK_AUTHENTICATION_PROOF:
return new TargetRequestsKeycloakAuthenticationProof(encodedParts);
default:
throw new DecodingException();
}
Expand All @@ -82,7 +84,8 @@ public enum Step {
SOURCE_SAS_INPUT(4),
TARGET_SHOW_SAS(5),
SOURCE_SNAPSHOT_SENT(6),
TARGET_SNAPSHOT_RECEIVED(7);
TARGET_SNAPSHOT_RECEIVED(7),
TARGET_REQUESTS_KEYCLOAK_AUTHENTICATION_PROOF(8);

private static final Map<Integer, Step> valueMap = new HashMap<>();
static {
Expand Down Expand Up @@ -298,6 +301,63 @@ public Encoded[] getEncodedParts() {
}
}

public static class TargetRequestsKeycloakAuthenticationProof extends ObvTransferStep {
public final String keycloakServerUrl;
public final String clientId;
public final String fullSas;
public final long sessionNumber;
public final String clientSecret; // may be null

public TargetRequestsKeycloakAuthenticationProof(String keycloakServerUrl, String clientId, String clientSecret, String fullSas, long sessionNumber) {
this.keycloakServerUrl = keycloakServerUrl;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.fullSas = fullSas;
this.sessionNumber = sessionNumber;
}

public TargetRequestsKeycloakAuthenticationProof(Encoded[] encodedParts) throws DecodingException {
if (encodedParts.length != 5 && encodedParts.length != 4) {
throw new DecodingException();
}
this.keycloakServerUrl = encodedParts[0].decodeString();
this.clientId = encodedParts[1].decodeString();
this.fullSas = encodedParts[2].decodeString();
this.sessionNumber = encodedParts[3].decodeLong();
if (encodedParts.length == 5) {
this.clientSecret = encodedParts[4].decodeString();
} else {
this.clientSecret = null;
}
}

@Override
public Step getStep() {
return Step.TARGET_REQUESTS_KEYCLOAK_AUTHENTICATION_PROOF;
}

@Override
public Encoded[] getEncodedParts() {
if (clientSecret == null) {
return new Encoded[]{
Encoded.of(keycloakServerUrl),
Encoded.of(clientId),
Encoded.of(fullSas),
Encoded.of(sessionNumber),
};
} else {
return new Encoded[]{
Encoded.of(keycloakServerUrl),
Encoded.of(clientId),
Encoded.of(fullSas),
Encoded.of(sessionNumber),
Encoded.of(clientSecret),
};
}
}
}


public static class TargetSnapshotReceived extends ObvTransferStep {
public TargetSnapshotReceived() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,19 @@ public class ObvKeycloakState {
public final JsonWebKeySet jwks; // non-null --> only set to null when sending to app and deserialization failed
public final JsonWebKey signatureKey; // non-null --> only set to null when sending to app and deserialization failed
public final String serializedAuthState; // device dependant --> do not share with other devices
public final boolean transferRestricted;
public final String ownApiKey; // not included in the serialized version
public final long latestRevocationListTimestamp; // not included in the serialized version
public final long latestGroupUpdateTimestamp; // not included in the serialized version

public ObvKeycloakState(String keycloakServer, String clientId, String clientSecret, JsonWebKeySet jwks, JsonWebKey signatureKey, String serializedAuthState, String ownApiKey, long latestRevocationListTimestamp, long latestGroupUpdateTimestamp) {
public ObvKeycloakState(String keycloakServer, String clientId, String clientSecret, JsonWebKeySet jwks, JsonWebKey signatureKey, String serializedAuthState, boolean transferRestricted, String ownApiKey, long latestRevocationListTimestamp, long latestGroupUpdateTimestamp) {
this.keycloakServer = keycloakServer;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.jwks = jwks;
this.signatureKey = signatureKey;
this.serializedAuthState = serializedAuthState;
this.transferRestricted = transferRestricted;
this.ownApiKey = ownApiKey;
this.latestRevocationListTimestamp = latestRevocationListTimestamp;
this.latestGroupUpdateTimestamp = latestGroupUpdateTimestamp;
Expand All @@ -73,6 +75,9 @@ public Encoded encode() {
if (serializedAuthState != null) {
dict.put(new DictionaryKey("sas"), Encoded.of(serializedAuthState));
}
if (transferRestricted) {
dict.put(new DictionaryKey("tr"), Encoded.of(transferRestricted));
}
return Encoded.of(dict);
}

Expand All @@ -83,6 +88,7 @@ public static ObvKeycloakState of(Encoded encoded) throws DecodingException {
JsonWebKeySet jwks;
JsonWebKey signatureKey;
final String serializedAuthState;
boolean transferRestricted;

HashMap<DictionaryKey, Encoded> dict = encoded.decodeDictionary();
DictionaryKey key = new DictionaryKey("ks");
Expand Down Expand Up @@ -135,7 +141,13 @@ public static ObvKeycloakState of(Encoded encoded) throws DecodingException {
} else {
serializedAuthState = null;
}

return new ObvKeycloakState(keycloakServer, clientId, clientSecret, jwks, signatureKey, serializedAuthState, null, 0, 0);
key = new DictionaryKey("tr");
encodedValue = dict.get(key);
if (encodedValue != null) {
transferRestricted = encodedValue.decodeBoolean();
} else {
transferRestricted = false;
}
return new ObvKeycloakState(keycloakServer, clientId, clientSecret, jwks, signatureKey, serializedAuthState, transferRestricted, null, 0, 0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ public Identity generateOwnedIdentity(Session session, String server, JsonIdenti
}

if (keycloakState != null) {
KeycloakServer keycloakServer = KeycloakServer.create(wrapSession(session), keycloakState.keycloakServer, ownedIdentity.getOwnedIdentity(), keycloakState.jwks.toJson(), keycloakState.signatureKey == null ? null : keycloakState.signatureKey.toJson(), keycloakState.clientId, keycloakState.clientSecret);
KeycloakServer keycloakServer = KeycloakServer.create(wrapSession(session), keycloakState.keycloakServer, ownedIdentity.getOwnedIdentity(), keycloakState.jwks.toJson(), keycloakState.signatureKey == null ? null : keycloakState.signatureKey.toJson(), keycloakState.clientId, keycloakState.clientSecret, keycloakState.transferRestricted);
if (keycloakServer == null) {
return null;
}
Expand Down Expand Up @@ -890,7 +890,7 @@ public void verifyAndAddRevocationList(Session session, Identity ownedIdentity,
}

@Override
public JsonKeycloakUserDetails verifyKeycloakSignature(Session session, Identity ownedIdentity, String signature) {
public JsonKeycloakUserDetails verifyKeycloakIdentitySignature(Session session, Identity ownedIdentity, String signature) {
try {
OwnedIdentity ownedIdentityObject = OwnedIdentity.get(wrapSession(session), ownedIdentity);
if (ownedIdentityObject == null || !ownedIdentityObject.isKeycloakManaged()) {
Expand Down Expand Up @@ -953,6 +953,37 @@ public JsonKeycloakUserDetails verifyKeycloakSignature(Session session, Identity
}


@Override
public String verifyKeycloakSignature(Session session, Identity ownedIdentity, String signature) {
try {
OwnedIdentity ownedIdentityObject = OwnedIdentity.get(wrapSession(session), ownedIdentity);
if (ownedIdentityObject == null || !ownedIdentityObject.isKeycloakManaged()) {
return null;
}
KeycloakServer keycloakServer = ownedIdentityObject.getKeycloakServer();

final JwksVerificationKeyResolver jwksResolver;
JsonWebKey signatureKey = keycloakServer.getSignatureKey();
if (signatureKey != null) {
jwksResolver = new JwksVerificationKeyResolver(Collections.singletonList(signatureKey));
} else {
JsonWebKeySet jwks = keycloakServer.getJwks();
jwksResolver = new JwksVerificationKeyResolver(jwks.getJsonWebKeys());
}
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setExpectedAudience(false)
.setVerificationKeyResolver(jwksResolver)
.build();

JwtContext context = jwtConsumer.process(signature);
if (context.getJwtClaims() != null) {
// signature is valid
return context.getJwtClaims().getRawJson();
}
} catch (Exception ignored) { }
return null;
}

@Override
public String getOwnedIdentityKeycloakServerUrl(Session session, Identity ownedIdentity) throws SQLException {
OwnedIdentity ownedIdentityObject = OwnedIdentity.get(wrapSession(session), ownedIdentity);
Expand Down Expand Up @@ -1026,7 +1057,7 @@ public void bindOwnedIdentityToKeycloak(Session session, Identity ownedIdentity,

session.addSessionCommitListener(backupNeededSessionCommitListener);

KeycloakServer keycloakServer = KeycloakServer.create(wrapSession(session), keycloakState.keycloakServer, ownedIdentity, keycloakState.jwks.toJson(), keycloakState.signatureKey == null ? null : keycloakState.signatureKey.toJson(), keycloakState.clientId, keycloakState.clientSecret);
KeycloakServer keycloakServer = KeycloakServer.create(wrapSession(session), keycloakState.keycloakServer, ownedIdentity, keycloakState.jwks.toJson(), keycloakState.signatureKey == null ? null : keycloakState.signatureKey.toJson(), keycloakState.clientId, keycloakState.clientSecret, keycloakState.transferRestricted);
if (keycloakServer == null) {
Logger.e("Unable to create new KeycloakServer db entry");
throw new Exception();
Expand Down Expand Up @@ -1092,6 +1123,16 @@ public JsonIdentityDetailsWithVersionAndPhoto[] getOwnedIdentityPublishedAndLate
return null;
}

@Override
public void updateKeycloakTransferRestrictedIfNeeded(Session session, Identity ownedIdentity, String serverUrl, boolean transferRestricted) throws SQLException {
KeycloakServer keycloakServer = KeycloakServer.get(wrapSession(session), serverUrl, ownedIdentity);

if (keycloakServer != null) {
if (transferRestricted ^ keycloakServer.isTransferRestricted()) {
keycloakServer.setTransferRestricted(transferRestricted);
}
}
}

@Override
public boolean updateKeycloakPushTopicsIfNeeded(Session session, Identity ownedIdentity, String serverUrl, List<String> pushTopics) throws SQLException {
Expand Down Expand Up @@ -1720,7 +1761,7 @@ public void reCheckAllCertifiedByOwnKeycloakContacts(Session session, Identity o
if (publishedDetails != null) {
JsonIdentityDetails identityDetails = publishedDetails.getJsonIdentityDetails();
if (identityDetails != null && identityDetails.getSignedUserDetails() != null) {
JsonKeycloakUserDetails jsonKeycloakUserDetails = verifyKeycloakSignature(session, ownedIdentity, identityDetails.getSignedUserDetails());
JsonKeycloakUserDetails jsonKeycloakUserDetails = verifyKeycloakIdentitySignature(session, ownedIdentity, identityDetails.getSignedUserDetails());

if (jsonKeycloakUserDetails != null) {
// the contact has some valid signed details
Expand Down
Loading

0 comments on commit 903ccd8

Please sign in to comment.