Skip to content

Commit

Permalink
FIP: Support mix of Accounts & Personas (#306)
Browse files Browse the repository at this point in the history
FIP: Support mix of Accounts & Personas + ROLA key support
  • Loading branch information
CyonAlexRDX authored Dec 20, 2024
1 parent c4ed26a commit 87a2400
Show file tree
Hide file tree
Showing 67 changed files with 2,633 additions and 737 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

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

Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ struct ShieldTests {

#expect(builder.validate() == .ConfirmationRoleMustHaveAtLeastOneFactor)
builder.addFactorSourceToConfirmationOverride(factorSourceId: .sampleArculus)

builder.setAuthenticationSigningFactor(new: .sampleDevice)

#expect(builder.validate() == nil)
#expect((try? builder.build()) != nil)
}
Expand Down Expand Up @@ -173,6 +176,8 @@ struct ShieldTests {
builder.addFactorSourceToRecoveryOverride(factorSourceId: .sampleArculus)
builder.addFactorSourceToConfirmationOverride(factorSourceId: .sampleArculusOther)

builder.setAuthenticationSigningFactor(new: .sampleDevice)

let shield = try! builder.build()

#expect(shield.matrixOfFactors.primaryRole.overrideFactors.isEmpty)
Expand Down Expand Up @@ -205,6 +210,8 @@ struct ShieldTests {
builder.removeFactorFromPrimary(factorSourceId: .sampleArculusOther)
builder.removeFactorFromRecovery(factorSourceId: .sampleLedgerOther)

builder.setAuthenticationSigningFactor(new: .sampleDevice)

// Validate
#expect(builder.validate() == nil)

Expand Down
2 changes: 1 addition & 1 deletion crates/sargon-uniffi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "sargon-uniffi"
# Don't forget to update version in crates/sargon/Cargo.toml
version = "1.1.92"
version = "1.1.93"
edition = "2021"
build = "build.rs"

Expand Down
9 changes: 9 additions & 0 deletions crates/sargon-uniffi/src/core/error/common_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,15 @@ pub enum CommonError {

#[error("Cannot securify entity that has provisional security config")]
CannotSecurifyEntityHasProvisionalSecurityConfig = 10241,

#[error("Too few FactorInstances derived")]
TooFewFactorInstancesDerived = 10242,

#[error("Missing Authentication Signing FactorInstance mapping SecurityStructureOfFactorSources into SecurityStructureOfFactorInstances.")]
MissingRolaKeyForSecurityStructureOfFactorInstances = 10243,

#[error("SecurityStateAccessController address mismatch")]
SecurityStateAccessControllerAddressMismatch = 10244,
}

#[uniffi::export]
Expand Down
4 changes: 4 additions & 0 deletions crates/sargon-uniffi/src/keys_collector/derivation_purpose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ pub enum DerivationPurpose {
/// for identity MFA
SecurifyingPersona,

/// When applying a security shield to accounts and personas mixed, initiates keys collection
/// for account MFA
SecurifyingAccountsAndPersonas,

/// When adding a new factor source, initiates keys collection
/// for collecting various factor instances.
PreDerivingKeys,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ impl SecurityShieldBuilder {
self.get(|builder| builder.get_name())
}

pub fn get_authentication_signing_factor(&self) -> Option<FactorSourceID> {
self.get(|builder| builder.get_authentication_signing_factor())
.map(|x| x.into())
}

pub fn get_primary_threshold_factors(&self) -> Vec<FactorSourceID> {
self.get_factors(|builder| builder.get_primary_threshold_factors())
}
Expand All @@ -135,6 +140,17 @@ impl SecurityShieldBuilder {
self.set(|builder| builder.set_name(&name));
}

pub fn set_authentication_signing_factor(
&self,
new: Option<FactorSourceID>,
) {
self.set(|builder| {
builder.set_authentication_signing_factor(
new.clone().map(|x| x.into_internal()),
)
});
}

pub fn remove_factor_from_all_roles(
&self,
factor_source_id: FactorSourceID,
Expand Down Expand Up @@ -756,6 +772,18 @@ mod tests {
sut.remove_factor_from_confirmation(f.clone());
assert_eq!(xs, sut.get_confirmation_factors());

assert_eq!(
sut.validate().unwrap(),
SecurityShieldBuilderInvalidReason::MissingAuthSigningFactor
);
sut.set_authentication_signing_factor(Some(
FactorSourceID::sample_device_other(),
));
assert_eq!(
sut.get_authentication_signing_factor(),
Some(FactorSourceID::sample_device_other())
);

let v0 = sut.validate();
let v1 = sut.validate(); // can call validate many times!
assert_eq!(v0, v1);
Expand All @@ -764,6 +792,10 @@ mod tests {
let shield = sut.build().unwrap(); // can call build many times!
assert_eq!(shield0, shield);

assert_eq!(
shield.authentication_signing_factor,
FactorSourceID::sample_device_other()
);
assert_eq!(shield.metadata.display_name.value, "S.H.I.E.L.D.");
assert_eq!(
shield.matrix_of_factors.primary_role.override_factors,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ use thiserror::Error as ThisError;
Clone, Debug, ThisError, PartialEq, InternalConversion, uniffi::Error,
)]
pub enum SecurityShieldBuilderInvalidReason {
#[error("Auth Signing Factor Missing")]
MissingAuthSigningFactor,

#[error("Shield name is invalid")]
ShieldNameInvalid,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub struct SecurityStructureOfFactorSourceIDs {
/// The structure of factors to use for certain roles, Primary, Recovery
/// and Confirmation role.
pub matrix_of_factors: MatrixOfFactorSourceIDs,

/// The factor to use for authentication signing aka true Rola Key.
pub authentication_signing_factor: FactorSourceID,
}

delegate_debug_into!(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ pub struct SecurityStructureOfFactorSources {
/// The structure of factors to use for certain roles, Primary, Recovery
/// and Confirmation role.
pub matrix_of_factors: MatrixOfFactorSources,

/// The factor to use for authentication signing aka true Rola Key.
pub authentication_signing_factor: FactorSource,
}

#[uniffi::export]
Expand Down
2 changes: 1 addition & 1 deletion crates/sargon/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "sargon"
# Don't forget to update version in crates/sargon-uniffi/Cargo.toml
version = "1.1.92"
version = "1.1.93"
edition = "2021"
build = "build.rs"

Expand Down
9 changes: 9 additions & 0 deletions crates/sargon/src/core/error/common_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,15 @@ pub enum CommonError {

#[error("Cannot securify entity that has provisional security config")]
CannotSecurifyEntityHasProvisionalSecurityConfig = 10241,

#[error("Too few FactorInstances derived")]
TooFewFactorInstancesDerived = 10242,

#[error("Missing Authentication Signing FactorInstance mapping SecurityStructureOfFactorSources into SecurityStructureOfFactorInstances.")]
MissingRolaKeyForSecurityStructureOfFactorInstances = 10243,

#[error("SecurityStateAccessController address mismatch")]
SecurityStateAccessControllerAddressMismatch = 10244,
}

impl CommonError {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ mod tests {
fn public_key() {
let private_key = SUT::sample();
let public_key = private_key.public_key();
println!("{:?}", public_key);
assert_eq!(
public_key,
KeyAgreementPublicKey::from_hex("8679bc1fe3210b2ce84793668b05218fdc4c220bc05387b7d2ac0d4c7b7c5d10".to_owned())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ pub enum DerivationPreset {
#[debug("A-MFA")]
AccountMfa,

/// Used to form DerivationPaths used to derive FactorInstances
/// for Authentication Signing (Securified) for accounts
/// `(EntityKind::Account, KeySpace::Securified, KeyKind::AuthenticationSigning)`
#[debug("A-Rola")]
AccountRola,

/// Used to form DerivationPaths used to derive FactorInstances
/// for "veci": Virtual Entity Creating (Factor)Instance for personas.
/// `(EntityKind::Identity, KeySpace::Unsecurified, KeyKind::TransactionSigning)`
Expand All @@ -35,6 +41,12 @@ pub enum DerivationPreset {
/// `(EntityKind::Identity, KeySpace::Securified, KeyKind::TransactionSigning)`
#[debug("I-MFA")]
IdentityMfa,

/// Used to form DerivationPaths used to derive FactorInstances
/// for Authentication Signing (Securified) for peresonas
/// `(EntityKind::Identity, KeySpace::Securified, KeyKind::AuthenticationSigning)`
#[debug("I-Rola")]
IdentityRola,
}

// =============
Expand Down Expand Up @@ -63,6 +75,15 @@ impl DerivationPreset {
CAP26EntityKind::Identity => Self::IdentityMfa,
}
}

/// Selects a `DerivationPreset` for MFA based on `CAP26EntityKind`,
/// i.e. either `DerivationPreset::AccountRola` or `DerivationPreset::IdentityRola`.
pub fn rola_entity_kind(entity_kind: CAP26EntityKind) -> Self {
match entity_kind {
CAP26EntityKind::Account => Self::AccountRola,
CAP26EntityKind::Identity => Self::IdentityRola,
}
}
}

// =============
Expand All @@ -72,8 +93,12 @@ impl DerivationPreset {
/// Returns the `CAP26EntityKind` of the `DerivationPreset`.
pub fn entity_kind(&self) -> CAP26EntityKind {
match self {
Self::AccountVeci | Self::AccountMfa => CAP26EntityKind::Account,
Self::IdentityVeci | Self::IdentityMfa => CAP26EntityKind::Identity,
Self::AccountVeci | Self::AccountMfa | Self::AccountRola => {
CAP26EntityKind::Account
}
Self::IdentityVeci | Self::IdentityMfa | Self::IdentityRola => {
CAP26EntityKind::Identity
}
}
}

Expand All @@ -84,6 +109,9 @@ impl DerivationPreset {
| Self::IdentityVeci
| Self::AccountMfa
| Self::IdentityMfa => CAP26KeyKind::TransactionSigning,
Self::AccountRola | Self::IdentityRola => {
CAP26KeyKind::AuthenticationSigning
}
}
}

Expand All @@ -96,7 +124,10 @@ impl DerivationPreset {
is_hardened: true,
}
}
Self::AccountMfa | Self::IdentityMfa => KeySpace::Securified,
Self::AccountMfa
| Self::IdentityMfa
| Self::AccountRola
| Self::IdentityRola => KeySpace::Securified,
}
}

Expand Down Expand Up @@ -136,4 +167,28 @@ mod tests {
fn inequality() {
assert_ne!(SUT::sample(), SUT::sample_other());
}

#[test]
fn test_mfa_entity_kind() {
assert_eq!(
SUT::mfa_entity_kind(CAP26EntityKind::Account),
SUT::AccountMfa
);
assert_eq!(
SUT::mfa_entity_kind(CAP26EntityKind::Identity),
SUT::IdentityMfa
);
}

#[test]
fn test_rola_entity_kind() {
assert_eq!(
SUT::rola_entity_kind(CAP26EntityKind::Account),
SUT::AccountRola
);
assert_eq!(
SUT::rola_entity_kind(CAP26EntityKind::Identity),
SUT::IdentityRola
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,16 @@ impl TryFrom<IndexAgnosticPath> for DerivationPreset {
CAP26KeyKind::TransactionSigning,
KeySpace::Securified,
) => Ok(DerivationPreset::IdentityMfa),
(
CAP26EntityKind::Account,
CAP26KeyKind::AuthenticationSigning,
KeySpace::Securified,
) => Ok(DerivationPreset::AccountRola),
(
CAP26EntityKind::Identity,
CAP26KeyKind::AuthenticationSigning,
KeySpace::Securified,
) => Ok(DerivationPreset::IdentityRola),
_ => Err(CommonError::InvalidBIP32Path {
bad_value:
"Invalid combination of entity_kind, key_kind and key_space"
Expand Down Expand Up @@ -334,4 +344,39 @@ mod tests {
assert_json_value_fails::<SUT>(json!(""));
assert_json_value_fails::<SUT>(json!(" "));
}

#[test]
fn derivation_preset_rola_valid_account() {
let preset = DerivationPreset::try_from(SUT::new(
NetworkID::Mainnet,
CAP26EntityKind::Account,
CAP26KeyKind::AuthenticationSigning,
KeySpace::Securified,
))
.unwrap();
assert_eq!(preset, DerivationPreset::AccountRola);
}

#[test]
fn derivation_preset_rola_invalid_not_securified() {
let res = DerivationPreset::try_from(SUT::new(
NetworkID::Mainnet,
CAP26EntityKind::Account,
CAP26KeyKind::AuthenticationSigning,
KeySpace::Unsecurified { is_hardened: true },
));
assert!(matches!(res, Err(CommonError::InvalidBIP32Path { .. })));
}

#[test]
fn derivation_preset_rola_valid_identity() {
let preset = DerivationPreset::try_from(SUT::new(
NetworkID::Mainnet,
CAP26EntityKind::Identity,
CAP26KeyKind::AuthenticationSigning,
KeySpace::Securified,
))
.unwrap();
assert_eq!(preset, DerivationPreset::IdentityRola);
}
}
Loading

0 comments on commit 87a2400

Please sign in to comment.