Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow changing PIN number if signed in within last 5 minutes #6459

Merged
merged 8 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ members = [
"backend/libraries/human_readable_derive",
"backend/libraries/icdex_client",
"backend/libraries/icpswap_client",
"backend/libraries/identity_utils",
"backend/libraries/index_store",
"backend/libraries/instruction_counts_log",
"backend/libraries/json",
Expand Down
1 change: 1 addition & 0 deletions backend/canisters/identity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed

- Increase max stable memory read / write buffer size ([#6440](https://github.com/open-chat-labs/open-chat/pull/6440))
- Extract certificate handling code into `identity_utils` ([#6459](https://github.com/open-chat-labs/open-chat/pull/6459))

## [[2.0.1344](https://github.com/open-chat-labs/open-chat/releases/tag/v2.0.1344-identity)] - 2024-09-10

Expand Down
15 changes: 0 additions & 15 deletions backend/canisters/identity/api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use candid::{CandidType, Deserialize};
use serde::Serialize;
use types::TimestampNanos;

mod lifecycle;
mod queries;
Expand All @@ -10,20 +9,6 @@ pub use lifecycle::*;
pub use queries::*;
pub use updates::*;

#[derive(CandidType, Serialize, Deserialize, Clone, Debug)]
pub struct Delegation {
#[serde(with = "serde_bytes")]
pub pubkey: Vec<u8>,
pub expiration: TimestampNanos,
}

#[derive(CandidType, Serialize, Deserialize, Clone, Debug)]
pub struct SignedDelegation {
pub delegation: Delegation,
#[serde(with = "serde_bytes")]
pub signature: Vec<u8>,
}

pub type ChallengeKey = u32;

#[derive(CandidType, Serialize, Deserialize, Debug)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::SignedDelegation;
use candid::CandidType;
use serde::{Deserialize, Serialize};
use types::TimestampNanos;
use types::{SignedDelegation, TimestampNanos};

#[derive(CandidType, Serialize, Deserialize, Debug)]
pub struct Args {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::SignedDelegation;
use candid::{CandidType, Deserialize, Principal};
use serde::Serialize;
use types::SignedDelegation;

#[derive(CandidType, Serialize, Deserialize, Debug)]
pub struct Args {
Expand Down
2 changes: 1 addition & 1 deletion backend/canisters/identity/impl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ canister_state_macros = { path = "../../../libraries/canister_state_macros" }
canister_tracing_macros = { path = "../../../libraries/canister_tracing_macros" }
http_request = { path = "../../../libraries/http_request" }
ic-captcha = { workspace = true }
ic-cbor = { workspace = true }
ic-cdk = { workspace = true }
ic-cdk-timers = { workspace = true }
ic-certificate-verification = { workspace = true }
ic-certification = { workspace = true }
ic-stable-structures = { workspace = true }
identity_canister = { path = "../api" }
identity_utils = { path = "../../../libraries/identity_utils" }
msgpack = { path = "../../../libraries/msgpack" }
rand = { workspace = true }
serde = { workspace = true }
Expand Down
3 changes: 1 addition & 2 deletions backend/canisters/identity/impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ use canister_sig_util::signature_map::{SignatureMap, LABEL_SIG};
use canister_sig_util::CanisterSigPublicKey;
use canister_state_macros::canister_state;
use ic_cdk::api::set_certified_data;
use identity_canister::Delegation;
use serde::{Deserialize, Serialize};
use sha256::sha256;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use types::{BuildVersion, CanisterId, Cycles, Hash, TimestampMillis, Timestamped};
use types::{BuildVersion, CanisterId, Cycles, Delegation, Hash, TimestampMillis, Timestamped};
use utils::consts::IC_ROOT_KEY;
use utils::env::Environment;
use x509_parser::prelude::{FromDer, SubjectPublicKeyInfo};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{delegation_signature_msg_hash, read_state, RuntimeState};
use ic_cdk::query;
use identity_canister::get_delegation::{Response::*, *};
use identity_canister::{Delegation, SignedDelegation};
use types::{Delegation, SignedDelegation};

#[query]
fn get_delegation(args: Args) -> Response {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use crate::{mutate_state, RuntimeState};
use canister_tracing_macros::trace;
use ic_cbor::{parse_cbor, CborValue, CertificateToCbor};
use ic_cdk::update;
use ic_certificate_verification::VerifyCertificate;
use ic_certification::Certificate;
use identity_canister::approve_identity_link::{Response::*, *};
use identity_utils::extract_certificate;
use utils::time::{MINUTE_IN_MS, NANOS_PER_MILLISECOND};

#[update]
Expand All @@ -20,21 +19,9 @@ fn approve_identity_link_impl(args: Args, state: &mut RuntimeState) -> Response
return CallerNotRecognised;
};

let now = state.env.now();
let now_nanos = (now * NANOS_PER_MILLISECOND) as u128;
let five_minutes = (5 * MINUTE_IN_MS * NANOS_PER_MILLISECOND) as u128;

let Ok(cbor) = parse_cbor(&args.delegation.signature) else {
return MalformedSignature("Unable to parse signature as CBOR".to_string());
};
let CborValue::Map(map) = cbor else {
return MalformedSignature("Expected CBOR map".to_string());
};
let Some(CborValue::ByteString(certificate_bytes)) = map.get("certificate") else {
return MalformedSignature("Couldn't find certificate".to_string());
};
let Ok(certificate) = Certificate::from_cbor(certificate_bytes) else {
return MalformedSignature("Unable to parse certificate".to_string());
let certificate = match extract_certificate(&args.delegation.signature) {
Ok(c) => c,
Err(e) => return MalformedSignature(e),
};
if certificate
.verify(
Expand All @@ -45,6 +32,11 @@ fn approve_identity_link_impl(args: Args, state: &mut RuntimeState) -> Response
{
return InvalidSignature;
}

let now = state.env.now();
let now_nanos = (now * NANOS_PER_MILLISECOND) as u128;
let five_minutes = (5 * MINUTE_IN_MS * NANOS_PER_MILLISECOND) as u128;

if ic_certificate_verification::validate_certificate_time(&certificate, &now_nanos, &five_minutes).is_err() {
return DelegationTooOld;
}
Expand Down
4 changes: 4 additions & 0 deletions backend/canisters/user/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [unreleased]

### Added

- Allow changing PIN number if signed in within last 5 minutes ([#6459](https://github.com/open-chat-labs/open-chat/pull/6459))

### Changed

- Increase max stable memory read / write buffer size ([#6440](https://github.com/open-chat-labs/open-chat/pull/6440))
Expand Down
16 changes: 15 additions & 1 deletion backend/canisters/user/api/can.did
Original file line number Diff line number Diff line change
Expand Up @@ -635,8 +635,20 @@ type CancelMessageReminderResponse = variant {
};

type SetPinNumberArgs = record {
current : opt text;
new : opt text;
verification : variant {
None;
PIN : text;
Delegation : SignedDelegation;
};
};

type SignedDelegation = record {
delegation : record {
pubkey : blob;
expiration : TimestampNanos;
};
signature : blob;
};

type SetPinNumberResponse = variant {
Expand All @@ -646,6 +658,8 @@ type SetPinNumberResponse = variant {
PinRequired;
PinIncorrect : Milliseconds;
TooManyFailedPinAttempts : Milliseconds;
MalformedSignature : text;
DelegationTooOld;
};

type SendMessageWithTransferToChannelArgs = record {
Expand Down
14 changes: 12 additions & 2 deletions backend/canisters/user/api/src/updates/set_pin_number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@ use candid::CandidType;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use ts_export::ts_export;
use types::{FieldTooLongResult, FieldTooShortResult, Milliseconds};
use types::{FieldTooLongResult, FieldTooShortResult, Milliseconds, SignedDelegation};

#[ts_export(user, set_pin_number)]
#[derive(CandidType, Serialize, Deserialize, Debug)]
pub struct Args {
pub current: Option<String>,
pub new: Option<String>,
pub verification: PinNumberVerification,
}

#[ts_export(user, set_pin_number)]
#[derive(CandidType, Serialize, Deserialize, Debug)]
pub enum PinNumberVerification {
None,
PIN(String),
Delegation(SignedDelegation),
}

#[ts_export(user, set_pin_number)]
Expand All @@ -20,4 +28,6 @@ pub enum Response {
PinRequired,
PinIncorrect(Milliseconds),
TooManyFailedPinAttempts(Milliseconds),
MalformedSignature(String),
DelegationTooOld,
}
2 changes: 2 additions & 0 deletions backend/canisters/user/impl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@ group_index_canister_c2c_client = { path = "../../group_index/c2c_client" }
http_request = { path = "../../../libraries/http_request" }
ic-cdk = { workspace = true }
ic-cdk-timers = { workspace = true }
ic-certificate-verification = { workspace = true }
ic-ledger-types = { workspace = true }
ic-stable-structures = { workspace = true }
icpswap_client = { path = "../../../libraries/icpswap_client" }
icrc_ledger_canister_c2c_client = { path = "../../../external_canisters/icrc_ledger/c2c_client" }
icrc_ledger_canister = { path = "../../../external_canisters/icrc_ledger/api" }
icrc-ledger-types = { workspace = true }
identity_utils = { path = "../../../libraries/identity_utils" }
itertools = { workspace = true }
ledger_utils = { path = "../../../libraries/ledger_utils" }
local_user_index_canister = { path = "../../local_user_index/api" }
Expand Down
64 changes: 43 additions & 21 deletions backend/canisters/user/impl/src/updates/set_pin_number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ use crate::model::pin_number::VerifyPinError;
use crate::{mutate_state, run_regular_jobs, RuntimeState};
use canister_api_macros::update;
use canister_tracing_macros::trace;
use identity_utils::extract_certificate;
use types::{FieldTooLongResult, FieldTooShortResult};
use user_canister::set_pin_number::{Response::*, *};
use utils::time::{MINUTE_IN_MS, NANOS_PER_MILLISECOND};

const MIN_LENGTH: usize = 4;
const MAX_LENGTH: usize = 20;
Expand All @@ -20,30 +22,50 @@ fn set_pin_number(args: Args) -> Response {
fn set_pin_number_impl(args: Args, state: &mut RuntimeState) -> Response {
let now = state.env.now();

if let Err(error) = state.data.pin_number.verify(args.current.as_deref(), now) {
match error {
VerifyPinError::PinRequired => PinRequired,
VerifyPinError::PinIncorrect(delay) => PinIncorrect(delay),
VerifyPinError::TooManyFailedAttempted(delay) => TooManyFailedPinAttempts(delay),
}
} else {
if let Some(new) = args.new.as_ref() {
let length = new.len();
if length < MIN_LENGTH {
return TooShort(FieldTooShortResult {
length_provided: length as u32,
min_length: MIN_LENGTH as u32,
});
if state.data.pin_number.enabled() {
match args.verification {
PinNumberVerification::None => return PinRequired,
hpeebles marked this conversation as resolved.
Show resolved Hide resolved
PinNumberVerification::PIN(attempt) => {
if let Err(error) = state.data.pin_number.verify(Some(&attempt), now) {
return match error {
VerifyPinError::PinRequired => PinRequired,
VerifyPinError::PinIncorrect(delay) => PinIncorrect(delay),
VerifyPinError::TooManyFailedAttempted(delay) => TooManyFailedPinAttempts(delay),
};
}
}
if length > MAX_LENGTH {
return TooLong(FieldTooLongResult {
length_provided: length as u32,
max_length: MAX_LENGTH as u32,
});
PinNumberVerification::Delegation(delegation) => {
let certificate = match extract_certificate(&delegation.signature) {
Ok(c) => c,
Err(e) => return MalformedSignature(e),
};

let now_nanos = (now * NANOS_PER_MILLISECOND) as u128;
let five_minutes = (5 * MINUTE_IN_MS * NANOS_PER_MILLISECOND) as u128;

if ic_certificate_verification::validate_certificate_time(&certificate, &now_nanos, &five_minutes).is_err() {
return DelegationTooOld;
};
}
}
}

state.data.pin_number.set(args.new, now);
Success
if let Some(new) = args.new.as_ref() {
let length = new.len();
if length < MIN_LENGTH {
return TooShort(FieldTooShortResult {
length_provided: length as u32,
min_length: MIN_LENGTH as u32,
});
}
if length > MAX_LENGTH {
return TooLong(FieldTooLongResult {
length_provided: length as u32,
max_length: MAX_LENGTH as u32,
});
}
}

state.data.pin_number.set(args.new, now);
Success
}
3 changes: 1 addition & 2 deletions backend/integration_tests/src/client/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ generate_update_call!(prepare_delegation);
pub mod happy_path {
use candid::Principal;
use identity_canister::auth_principals::UserPrincipal;
use identity_canister::SignedDelegation;
use pocket_ic::PocketIc;
use types::{CanisterId, TimestampMillis};
use types::{CanisterId, SignedDelegation, TimestampMillis};

pub fn create_identity(
env: &mut PocketIc,
Expand Down
Loading
Loading