Skip to content

Commit

Permalink
Allow changing PIN if signed in within last 5 minutes
Browse files Browse the repository at this point in the history
  • Loading branch information
hpeebles committed Sep 26, 2024
1 parent d982855 commit d5ae470
Show file tree
Hide file tree
Showing 16 changed files with 132 additions and 63 deletions.
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
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
3 changes: 1 addition & 2 deletions backend/canisters/identity/api/src/queries/get_delegation.rs
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
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,
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
}
10 changes: 10 additions & 0 deletions backend/libraries/identity_utils/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "identity_utils"
version = "0.1.0"
edition = "2021"

[dependencies]
ic-cbor = { workspace = true }
ic-certificate-verification = { workspace = true }
ic-certification = { workspace = true }
types = { path = "../types" }
15 changes: 15 additions & 0 deletions backend/libraries/identity_utils/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use ic_cbor::{parse_cbor, CborValue, CertificateToCbor};
use ic_certification::Certificate;

pub fn extract_certificate(signature: &[u8]) -> Result<Certificate, String> {
let Ok(cbor) = parse_cbor(signature) else {
return Err("Unable to parse signature as CBOR".to_string());
};
let CborValue::Map(map) = cbor else {
return Err("Expected CBOR map".to_string());
};
let Some(CborValue::ByteString(certificate_bytes)) = map.get("certificate") else {
return Err("Couldn't find certificate".to_string());
};
Certificate::from_cbor(certificate_bytes).map_err(|_| "Unable to parse certificate".to_string())
}
20 changes: 20 additions & 0 deletions backend/libraries/types/src/delegation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use crate::TimestampNanos;
use candid::{CandidType, Deserialize};
use serde::Serialize;
use ts_export::ts_export;

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

#[ts_export]
#[derive(CandidType, Serialize, Deserialize, Clone, Debug)]
pub struct SignedDelegation {
pub delegation: Delegation,
#[serde(with = "serde_bytes")]
pub signature: Vec<u8>,
}
2 changes: 2 additions & 0 deletions backend/libraries/types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ mod community_roles;
mod community_summary;
mod cryptocurrency;
mod cycles;
mod delegation;
mod deleted_group_info;
mod diamond_membership;
mod error;
Expand Down Expand Up @@ -94,6 +95,7 @@ pub use community_roles::*;
pub use community_summary::*;
pub use cryptocurrency::*;
pub use cycles::*;
pub use delegation::*;
pub use deleted_group_info::*;
pub use diamond_membership::*;
pub use error::*;
Expand Down

0 comments on commit d5ae470

Please sign in to comment.