Compare commits
1 Commits
security-h
...
check-uac-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e1ab51398 |
@@ -394,7 +394,6 @@ mod tests {
|
|||||||
chain: CHAIN_ID,
|
chain: CHAIN_ID,
|
||||||
valid_from: None,
|
valid_from: None,
|
||||||
valid_until: None,
|
valid_until: None,
|
||||||
revoked_at: None,
|
|
||||||
max_gas_fee_per_gas: None,
|
max_gas_fee_per_gas: None,
|
||||||
max_priority_fee_per_gas: None,
|
max_priority_fee_per_gas: None,
|
||||||
rate_limit: None,
|
rate_limit: None,
|
||||||
@@ -597,24 +596,4 @@ mod tests {
|
|||||||
assert!(violations.is_empty());
|
assert!(violations.is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn shared_settings_hash_changes_when_revoked_at_changes() {
|
|
||||||
use arbiter_crypto::hashing::Hashable;
|
|
||||||
use sha2::Digest;
|
|
||||||
|
|
||||||
let active = shared_settings();
|
|
||||||
let revoked = SharedGrantSettings {
|
|
||||||
revoked_at: Some(Utc::now()),
|
|
||||||
..shared_settings()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut active_hash = sha2::Sha256::new();
|
|
||||||
active.hash(&mut active_hash);
|
|
||||||
|
|
||||||
let mut revoked_hash = sha2::Sha256::new();
|
|
||||||
revoked.hash(&mut revoked_hash);
|
|
||||||
|
|
||||||
assert_ne!(active_hash.finalize(), revoked_hash.finalize());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,7 +146,6 @@ pub struct SharedGrantSettings {
|
|||||||
|
|
||||||
pub valid_from: Option<DateTime<Utc>>,
|
pub valid_from: Option<DateTime<Utc>>,
|
||||||
pub valid_until: Option<DateTime<Utc>>,
|
pub valid_until: Option<DateTime<Utc>>,
|
||||||
pub revoked_at: Option<DateTime<Utc>>,
|
|
||||||
|
|
||||||
pub max_gas_fee_per_gas: Option<U256>,
|
pub max_gas_fee_per_gas: Option<U256>,
|
||||||
pub max_priority_fee_per_gas: Option<U256>,
|
pub max_priority_fee_per_gas: Option<U256>,
|
||||||
@@ -161,7 +160,6 @@ impl SharedGrantSettings {
|
|||||||
chain: model.chain_id as u64, // safe because chain_id is stored as i32 but is guaranteed to be a valid ChainId by the API when creating grants
|
chain: model.chain_id as u64, // safe because chain_id is stored as i32 but is guaranteed to be a valid ChainId by the API when creating grants
|
||||||
valid_from: model.valid_from.map(Into::into),
|
valid_from: model.valid_from.map(Into::into),
|
||||||
valid_until: model.valid_until.map(Into::into),
|
valid_until: model.valid_until.map(Into::into),
|
||||||
revoked_at: model.revoked_at.map(Into::into),
|
|
||||||
max_gas_fee_per_gas: model
|
max_gas_fee_per_gas: model
|
||||||
.max_gas_fee_per_gas
|
.max_gas_fee_per_gas
|
||||||
.map(|b| utils::try_bytes_to_u256(&b))
|
.map(|b| utils::try_bytes_to_u256(&b))
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ fn shared() -> SharedGrantSettings {
|
|||||||
chain: CHAIN_ID,
|
chain: CHAIN_ID,
|
||||||
valid_from: None,
|
valid_from: None,
|
||||||
valid_until: None,
|
valid_until: None,
|
||||||
revoked_at: None,
|
|
||||||
max_gas_fee_per_gas: None,
|
max_gas_fee_per_gas: None,
|
||||||
max_priority_fee_per_gas: None,
|
max_priority_fee_per_gas: None,
|
||||||
rate_limit: None,
|
rate_limit: None,
|
||||||
|
|||||||
@@ -95,7 +95,6 @@ fn shared() -> SharedGrantSettings {
|
|||||||
chain: CHAIN_ID,
|
chain: CHAIN_ID,
|
||||||
valid_from: None,
|
valid_from: None,
|
||||||
valid_until: None,
|
valid_until: None,
|
||||||
revoked_at: None,
|
|
||||||
max_gas_fee_per_gas: None,
|
max_gas_fee_per_gas: None,
|
||||||
max_priority_fee_per_gas: None,
|
max_priority_fee_per_gas: None,
|
||||||
rate_limit: None,
|
rate_limit: None,
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ impl TryConvert for ProtoSharedSettings {
|
|||||||
.valid_until
|
.valid_until
|
||||||
.map(ProtoTimestamp::try_convert)
|
.map(ProtoTimestamp::try_convert)
|
||||||
.transpose()?,
|
.transpose()?,
|
||||||
revoked_at: None,
|
|
||||||
max_gas_fee_per_gas: self
|
max_gas_fee_per_gas: self
|
||||||
.max_gas_fee_per_gas
|
.max_gas_fee_per_gas
|
||||||
.as_deref()
|
.as_deref()
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'dart:convert';
|
|||||||
import 'package:arbiter/features/connection/connection.dart';
|
import 'package:arbiter/features/connection/connection.dart';
|
||||||
import 'package:arbiter/features/connection/server_info_storage.dart';
|
import 'package:arbiter/features/connection/server_info_storage.dart';
|
||||||
import 'package:arbiter/features/identity/pk_manager.dart';
|
import 'package:arbiter/features/identity/pk_manager.dart';
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:arbiter/proto/arbiter.pbgrpc.dart';
|
import 'package:arbiter/proto/arbiter.pbgrpc.dart';
|
||||||
import 'package:arbiter/proto/user_agent/auth.pb.dart' as ua_auth;
|
import 'package:arbiter/proto/user_agent/auth.pb.dart' as ua_auth;
|
||||||
import 'package:arbiter/proto/user_agent.pb.dart';
|
import 'package:arbiter/proto/user_agent.pb.dart';
|
||||||
@@ -45,6 +46,18 @@ class ConnectionException implements Exception {
|
|||||||
String toString() => message;
|
String toString() => message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String certificateFingerprintHex(List<int> derBytes) {
|
||||||
|
return sha256.convert(derBytes).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isPinnedServerCertificate({
|
||||||
|
required String expectedFingerprint,
|
||||||
|
required List<int> certificateDer,
|
||||||
|
}) {
|
||||||
|
return certificateFingerprintHex(certificateDer) ==
|
||||||
|
expectedFingerprint.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
Future<Connection> connectAndAuthorize(
|
Future<Connection> connectAndAuthorize(
|
||||||
StoredServerInfo serverInfo,
|
StoredServerInfo serverInfo,
|
||||||
KeyHandle key, {
|
KeyHandle key, {
|
||||||
@@ -155,7 +168,12 @@ Future<Connection> _connect(StoredServerInfo serverInfo) async {
|
|||||||
connectTimeout: const Duration(seconds: 10),
|
connectTimeout: const Duration(seconds: 10),
|
||||||
credentials: ChannelCredentials.secure(
|
credentials: ChannelCredentials.secure(
|
||||||
onBadCertificate: (cert, host) {
|
onBadCertificate: (cert, host) {
|
||||||
return true;
|
final isExpectedHost = host == serverInfo.address;
|
||||||
|
final isPinnedCert = isPinnedServerCertificate(
|
||||||
|
expectedFingerprint: serverInfo.caCertFingerprint,
|
||||||
|
certificateDer: cert.der,
|
||||||
|
);
|
||||||
|
return isExpectedHost && isPinnedCert;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import 'package:arbiter/features/connection/auth.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('certificate pinning helpers', () {
|
||||||
|
test('certificateFingerprintHex returns SHA-256 in hex', () {
|
||||||
|
final fingerprint = certificateFingerprintHex('abc'.codeUnits);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
fingerprint,
|
||||||
|
'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isPinnedServerCertificate matches expected fingerprint', () {
|
||||||
|
final matches = isPinnedServerCertificate(
|
||||||
|
expectedFingerprint:
|
||||||
|
'BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD',
|
||||||
|
certificateDer: 'abc'.codeUnits,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(matches, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isPinnedServerCertificate rejects mismatched fingerprint', () {
|
||||||
|
final matches = isPinnedServerCertificate(
|
||||||
|
expectedFingerprint:
|
||||||
|
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||||||
|
certificateDer: 'abc'.codeUnits,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(matches, isFalse);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user