mirror of
https://github.com/vector-im/hydrogen-web.git
synced 2025-02-08 18:48:18 +01:00
return enum explaining user trust level rather than boolean
This commit is contained in:
parent
e00d02a599
commit
bae18c037f
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {verifyEd25519Signature, getEd25519Signature, SIGNATURE_ALGORITHM} from "./common";
|
import {verifyEd25519Signature, getEd25519Signature, SIGNATURE_ALGORITHM, SignatureVerification} from "./common";
|
||||||
import {HistoryVisibility, shouldShareKey, DeviceKey, getDeviceEd25519Key, getDeviceCurve25519Key} from "./common";
|
import {HistoryVisibility, shouldShareKey, DeviceKey, getDeviceEd25519Key, getDeviceCurve25519Key} from "./common";
|
||||||
import {RoomMember} from "../room/members/RoomMember.js";
|
import {RoomMember} from "../room/members/RoomMember.js";
|
||||||
import {getKeyUsage, getKeyEd25519Key, getKeyUserId, KeyUsage} from "../verification/CrossSigning";
|
import {getKeyUsage, getKeyEd25519Key, getKeyUserId, KeyUsage} from "../verification/CrossSigning";
|
||||||
@ -462,7 +462,7 @@ export class DeviceTracker {
|
|||||||
log.log("ed25519 and/or curve25519 key invalid").set({deviceKey});
|
log.log("ed25519 and/or curve25519 key invalid").set({deviceKey});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const isValid = verifyEd25519Signature(this._olmUtil, userId, deviceId, ed25519Key, deviceKey, log);
|
const isValid = verifyEd25519Signature(this._olmUtil, userId, deviceId, ed25519Key, deviceKey, log) === SignatureVerification.Valid;
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
log.log({
|
log.log({
|
||||||
l: "ignore device with invalid signature",
|
l: "ignore device with invalid signature",
|
||||||
|
@ -68,11 +68,17 @@ export function getEd25519Signature(signedValue: SignedValue, userId: string, de
|
|||||||
return signedValue?.signatures?.[userId]?.[`${SIGNATURE_ALGORITHM}:${deviceOrKeyId}`];
|
return signedValue?.signatures?.[userId]?.[`${SIGNATURE_ALGORITHM}:${deviceOrKeyId}`];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function verifyEd25519Signature(olmUtil: Olm.Utility, userId: string, deviceOrKeyId: string, ed25519Key: string, value: SignedValue, log?: ILogItem) {
|
export enum SignatureVerification {
|
||||||
|
Valid,
|
||||||
|
Invalid,
|
||||||
|
NotSigned,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verifyEd25519Signature(olmUtil: Olm.Utility, userId: string, deviceOrKeyId: string, ed25519Key: string, value: SignedValue, log?: ILogItem): SignatureVerification {
|
||||||
const signature = getEd25519Signature(value, userId, deviceOrKeyId);
|
const signature = getEd25519Signature(value, userId, deviceOrKeyId);
|
||||||
if (!signature) {
|
if (!signature) {
|
||||||
log?.set("no_signature", true);
|
log?.set("no_signature", true);
|
||||||
return false;
|
return SignatureVerification.NotSigned;
|
||||||
}
|
}
|
||||||
const clone = Object.assign({}, value) as object;
|
const clone = Object.assign({}, value) as object;
|
||||||
delete clone["unsigned"];
|
delete clone["unsigned"];
|
||||||
@ -81,14 +87,14 @@ export function verifyEd25519Signature(olmUtil: Olm.Utility, userId: string, dev
|
|||||||
try {
|
try {
|
||||||
// throws when signature is invalid
|
// throws when signature is invalid
|
||||||
olmUtil.ed25519_verify(ed25519Key, canonicalJson, signature);
|
olmUtil.ed25519_verify(ed25519Key, canonicalJson, signature);
|
||||||
return true;
|
return SignatureVerification.Valid;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (log) {
|
if (log) {
|
||||||
const logItem = log.log({l: "Invalid signature, ignoring.", ed25519Key, canonicalJson, signature});
|
const logItem = log.log({l: "Invalid signature, ignoring.", ed25519Key, canonicalJson, signature});
|
||||||
logItem.error = err;
|
logItem.error = err;
|
||||||
logItem.logLevel = log.level.Warn;
|
logItem.logLevel = log.level.Warn;
|
||||||
}
|
}
|
||||||
return false;
|
return SignatureVerification.Invalid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {groupByWithCreator} from "../../../utils/groupBy";
|
import {groupByWithCreator} from "../../../utils/groupBy";
|
||||||
import {verifyEd25519Signature, OLM_ALGORITHM, getDeviceCurve25519Key, getDeviceEd25519Key} from "../common";
|
import {verifyEd25519Signature, OLM_ALGORITHM, getDeviceCurve25519Key, getDeviceEd25519Key, SignatureVerification} from "../common";
|
||||||
import {createSessionEntry} from "./Session";
|
import {createSessionEntry} from "./Session";
|
||||||
|
|
||||||
import type {OlmMessage, OlmPayload, OlmEncryptedMessageContent} from "./types";
|
import type {OlmMessage, OlmPayload, OlmEncryptedMessageContent} from "./types";
|
||||||
@ -260,7 +260,7 @@ export class Encryption {
|
|||||||
const device = devicesByUser.get(userId)?.get(deviceId);
|
const device = devicesByUser.get(userId)?.get(deviceId);
|
||||||
if (device) {
|
if (device) {
|
||||||
const isValidSignature = verifyEd25519Signature(
|
const isValidSignature = verifyEd25519Signature(
|
||||||
this.olmUtil, userId, deviceId, getDeviceEd25519Key(device), keySection, log);
|
this.olmUtil, userId, deviceId, getDeviceEd25519Key(device), keySection, log) === SignatureVerification.Valid;
|
||||||
if (isValidSignature) {
|
if (isValidSignature) {
|
||||||
const target = EncryptionTarget.fromOTK(device, keySection.key);
|
const target = EncryptionTarget.fromOTK(device, keySection.key);
|
||||||
verifiedEncryptionTargets.push(target);
|
verifiedEncryptionTargets.push(target);
|
||||||
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import { ILogItem } from "../../lib";
|
import { ILogItem } from "../../lib";
|
||||||
import {pkSign} from "./common";
|
import {pkSign} from "./common";
|
||||||
import {verifyEd25519Signature} from "../e2ee/common";
|
import {verifyEd25519Signature, SignatureVerification} from "../e2ee/common";
|
||||||
|
|
||||||
import type {SecretStorage} from "../ssss/SecretStorage";
|
import type {SecretStorage} from "../ssss/SecretStorage";
|
||||||
import type {Storage} from "../storage/idb/Storage";
|
import type {Storage} from "../storage/idb/Storage";
|
||||||
@ -43,6 +43,27 @@ export enum KeyUsage {
|
|||||||
UserSigning = "user_signing"
|
UserSigning = "user_signing"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum UserTrust {
|
||||||
|
/** We trust the user, the whole signature chain checks out from our MSK to all of their device keys. */
|
||||||
|
Trusted = 1,
|
||||||
|
/** We haven't signed this user's identity yet. Verify this user first to sign it. */
|
||||||
|
UserNotSigned,
|
||||||
|
/** We have signed the user already, but the signature isn't valid.
|
||||||
|
One possible cause could be that an attacker is uploading signatures in our name. */
|
||||||
|
UserSignatureMismatch,
|
||||||
|
/** We trust the user, but they don't trust one of their devices. */
|
||||||
|
UserDeviceNotSigned,
|
||||||
|
/** We trust the user, but the signatures of one of their devices is invalid.
|
||||||
|
* One possible cause could be that an attacker is uploading signatures in their name. */
|
||||||
|
UserDeviceSignatureMismatch,
|
||||||
|
/** The user doesn't have a valid signature for the SSK with their MSK, or the SSK is missing.
|
||||||
|
* This likely means bootstrapping cross-signing on their end didn't finish correctly. */
|
||||||
|
UserSetupError,
|
||||||
|
/** We don't have a valid signature for our SSK with our MSK, the SSK is missing, or we don't trust our own MSK.
|
||||||
|
* This likely means bootstrapping cross-signing on our end didn't finish correctly. */
|
||||||
|
OwnSetupError
|
||||||
|
}
|
||||||
|
|
||||||
export class CrossSigning {
|
export class CrossSigning {
|
||||||
private readonly storage: Storage;
|
private readonly storage: Storage;
|
||||||
private readonly secretStorage: SecretStorage;
|
private readonly secretStorage: SecretStorage;
|
||||||
@ -161,31 +182,69 @@ export class CrossSigning {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async isUserTrusted(userId: string, log: ILogItem): Promise<boolean> {
|
async getUserTrust(userId: string, log: ILogItem): Promise<UserTrust> {
|
||||||
return log.wrap("isUserTrusted", async log => {
|
return log.wrap("getUserTrust", async log => {
|
||||||
log.set("id", userId);
|
log.set("id", userId);
|
||||||
if (!this.isMasterKeyTrusted) {
|
if (!this.isMasterKeyTrusted) {
|
||||||
return false;
|
return UserTrust.OwnSetupError;
|
||||||
}
|
|
||||||
const theirDeviceKeys = await log.wrap("get their devices", log => this.deviceTracker.devicesForUsers([userId], this.hsApi, log));
|
|
||||||
const theirSSK = await log.wrap("get their ssk", log => this.deviceTracker.getCrossSigningKeyForUser(userId, KeyUsage.SelfSigning, this.hsApi, log));
|
|
||||||
if (!theirSSK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasUnsignedDevice = theirDeviceKeys.some(dk => log.wrap({l: "verify device", id: dk.device_id}, log => !this.hasValidSignatureFrom(dk, theirSSK, log)));
|
|
||||||
if (hasUnsignedDevice) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const theirMSK = await log.wrap("get their msk", log => this.deviceTracker.getCrossSigningKeyForUser(userId, KeyUsage.Master, this.hsApi, log));
|
|
||||||
if (!theirMSK || !log.wrap("verify their ssk", log => this.hasValidSignatureFrom(theirSSK, theirMSK, log))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const ourUSK = await log.wrap("get our usk", log => this.deviceTracker.getCrossSigningKeyForUser(this.ownUserId, KeyUsage.UserSigning, this.hsApi, log));
|
|
||||||
if (!ourUSK || !log.wrap("verify their msk", log => this.hasValidSignatureFrom(theirMSK, ourUSK, log))) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
const ourMSK = await log.wrap("get our msk", log => this.deviceTracker.getCrossSigningKeyForUser(this.ownUserId, KeyUsage.Master, this.hsApi, log));
|
const ourMSK = await log.wrap("get our msk", log => this.deviceTracker.getCrossSigningKeyForUser(this.ownUserId, KeyUsage.Master, this.hsApi, log));
|
||||||
return !!ourMSK && log.wrap("verify our usk", log => this.hasValidSignatureFrom(ourUSK, ourMSK, log));
|
if (!ourMSK) {
|
||||||
|
return UserTrust.OwnSetupError;
|
||||||
|
}
|
||||||
|
const ourUSK = await log.wrap("get our usk", log => this.deviceTracker.getCrossSigningKeyForUser(this.ownUserId, KeyUsage.UserSigning, this.hsApi, log));
|
||||||
|
if (!ourUSK) {
|
||||||
|
return UserTrust.OwnSetupError;
|
||||||
|
}
|
||||||
|
const ourUSKVerification = log.wrap("verify our usk", log => this.hasValidSignatureFrom(ourUSK, ourMSK, log));
|
||||||
|
if (ourUSKVerification !== SignatureVerification.Valid) {
|
||||||
|
return UserTrust.OwnSetupError;
|
||||||
|
}
|
||||||
|
const theirMSK = await log.wrap("get their msk", log => this.deviceTracker.getCrossSigningKeyForUser(userId, KeyUsage.Master, this.hsApi, log));
|
||||||
|
if (!theirMSK) {
|
||||||
|
/* assume that when they don't have an MSK, they've never enabled cross-signing on their client
|
||||||
|
(or it's not supported) rather than assuming a setup error on their side.
|
||||||
|
Later on, for their SSK, we _do_ assume it's a setup error as it doesn't make sense to have an MSK without a SSK */
|
||||||
|
return UserTrust.UserNotSigned;
|
||||||
|
}
|
||||||
|
const theirMSKVerification = log.wrap("verify their msk", log => this.hasValidSignatureFrom(theirMSK, ourUSK, log));
|
||||||
|
if (theirMSKVerification !== SignatureVerification.Valid) {
|
||||||
|
if (theirMSKVerification === SignatureVerification.NotSigned) {
|
||||||
|
return UserTrust.UserNotSigned;
|
||||||
|
} else { /* SignatureVerification.Invalid */
|
||||||
|
return UserTrust.UserSignatureMismatch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const theirSSK = await log.wrap("get their ssk", log => this.deviceTracker.getCrossSigningKeyForUser(userId, KeyUsage.SelfSigning, this.hsApi, log));
|
||||||
|
if (!theirSSK) {
|
||||||
|
return UserTrust.UserSetupError;
|
||||||
|
}
|
||||||
|
const theirSSKVerification = log.wrap("verify their ssk", log => this.hasValidSignatureFrom(theirSSK, theirMSK, log));
|
||||||
|
if (theirSSKVerification !== SignatureVerification.Valid) {
|
||||||
|
return UserTrust.UserSetupError;
|
||||||
|
}
|
||||||
|
const theirDeviceKeys = await log.wrap("get their devices", log => this.deviceTracker.devicesForUsers([userId], this.hsApi, log));
|
||||||
|
const lowestDeviceVerification = theirDeviceKeys.reduce((lowest, dk) => log.wrap({l: "verify device", id: dk.device_id}, log => {
|
||||||
|
const verification = this.hasValidSignatureFrom(dk, theirSSK, log);
|
||||||
|
// first Invalid, then NotSigned, then Valid
|
||||||
|
if (lowest === SignatureVerification.Invalid || verification === SignatureVerification.Invalid) {
|
||||||
|
return SignatureVerification.Invalid;
|
||||||
|
} else if (lowest === SignatureVerification.NotSigned || verification === SignatureVerification.NotSigned) {
|
||||||
|
return SignatureVerification.NotSigned;
|
||||||
|
} else if (lowest === SignatureVerification.Valid || verification === SignatureVerification.Valid) {
|
||||||
|
return SignatureVerification.Valid;
|
||||||
|
}
|
||||||
|
// should never happen as we went over all the enum options
|
||||||
|
return SignatureVerification.Invalid;
|
||||||
|
}), SignatureVerification.Valid);
|
||||||
|
if (lowestDeviceVerification !== SignatureVerification.Valid) {
|
||||||
|
if (lowestDeviceVerification === SignatureVerification.NotSigned) {
|
||||||
|
return UserTrust.UserDeviceNotSigned;
|
||||||
|
} else { /* SignatureVerification.Invalid */
|
||||||
|
return UserTrust.UserDeviceSignatureMismatch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UserTrust.Trusted;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,10 +276,10 @@ export class CrossSigning {
|
|||||||
pkSign(this.olm, keyToSign, signingKey, this.ownUserId, "");
|
pkSign(this.olm, keyToSign, signingKey, this.ownUserId, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
private hasValidSignatureFrom(key: DeviceKey | CrossSigningKey, signingKey: CrossSigningKey, log: ILogItem): boolean {
|
private hasValidSignatureFrom(key: DeviceKey | CrossSigningKey, signingKey: CrossSigningKey, log: ILogItem): SignatureVerification {
|
||||||
const pubKey = getKeyEd25519Key(signingKey);
|
const pubKey = getKeyEd25519Key(signingKey);
|
||||||
if (!pubKey) {
|
if (!pubKey) {
|
||||||
return false;
|
return SignatureVerification.NotSigned;
|
||||||
}
|
}
|
||||||
return verifyEd25519Signature(this.olmUtil, signingKey.user_id, pubKey, pubKey, key, log);
|
return verifyEd25519Signature(this.olmUtil, signingKey.user_id, pubKey, pubKey, key, log);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user