Skip to content

Commit 6b2b010

Browse files
authored
Merge pull request #15 from bitwarden/anders/add-ci-workflow
PM-32832: Add CI workflow for build, test, clippy, and formatting checks
2 parents cd8d65b + 3d2b822 commit 6b2b010

File tree

5 files changed

+160
-9
lines changed

5 files changed

+160
-9
lines changed

.claude/CLAUDE.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,17 @@ Storage directory: `~/.bw-remote/`
107107
- Session cache: `session_cache_{name}.json` — array of sessions keyed by `IdentityFingerprint`, with optional `PersistentTransportState` (CBOR) for session resumption without re-handshake
108108
- Transport auto-rekeys every 24 hours; replay nonces are not persisted (reset on load, 24h max message age)
109109

110+
## Before Committing
111+
112+
Always run these checks before committing:
113+
114+
```bash
115+
cargo fmt --all -- --check # Verify formatting
116+
cargo clippy --workspace # Lint check
117+
cargo build --workspace # Verify it compiles
118+
cargo test --workspace # Run all tests
119+
```
120+
110121
## Rust Conventions
111122

112123
- Edition 2024, minimum Rust version 1.85, toolchain channel 1.93

.github/workflows/ci.yml

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
name: CI
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches: ["main"]
7+
pull_request:
8+
9+
permissions: {}
10+
11+
env:
12+
CARGO_TERM_COLOR: always
13+
14+
jobs:
15+
fmt:
16+
name: Formatting
17+
runs-on: ubuntu-24.04
18+
19+
permissions:
20+
contents: read
21+
22+
steps:
23+
- name: Checkout
24+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
25+
with:
26+
persist-credentials: false
27+
28+
- name: Install rust
29+
uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # stable
30+
with:
31+
toolchain: stable
32+
components: rustfmt
33+
34+
- name: Cargo fmt
35+
run: cargo fmt --all -- --check
36+
37+
clippy:
38+
name: Clippy
39+
runs-on: ubuntu-24.04
40+
41+
permissions:
42+
contents: read
43+
44+
steps:
45+
- name: Checkout
46+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
47+
with:
48+
persist-credentials: false
49+
50+
- name: Install rust
51+
uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # stable
52+
with:
53+
toolchain: stable
54+
components: clippy
55+
56+
- name: Cache cargo registry
57+
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
58+
59+
- name: Cargo clippy
60+
run: cargo clippy --workspace -- -D warnings
61+
62+
build:
63+
name: Build
64+
runs-on: ubuntu-24.04
65+
66+
permissions:
67+
contents: read
68+
69+
steps:
70+
- name: Checkout
71+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
72+
with:
73+
persist-credentials: false
74+
75+
- name: Install rust
76+
uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # stable
77+
with:
78+
toolchain: stable
79+
80+
- name: Cache cargo registry
81+
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
82+
83+
- name: Build
84+
run: cargo build --workspace
85+
86+
test:
87+
name: Tests
88+
runs-on: ubuntu-24.04
89+
needs: build
90+
91+
permissions:
92+
contents: read
93+
94+
steps:
95+
- name: Checkout
96+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
97+
with:
98+
persist-credentials: false
99+
100+
- name: Install rust
101+
uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # stable
102+
with:
103+
toolchain: stable
104+
105+
- name: Cache cargo registry
106+
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
107+
108+
- name: Run tests
109+
run: cargo test --workspace

crates/bw-noise-protocol/src/handshake.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,12 @@ impl InitiatorHandshake {
120120
}
121121
}
122122

123+
impl Default for InitiatorHandshake {
124+
fn default() -> Self {
125+
Self::new()
126+
}
127+
}
128+
123129
/// The handshake from the responder side. The handshake consists of 2 messages:
124130
/// 1. HandshakeInit (I → R)
125131
/// 2. HandshakeResponse (R → I)
@@ -215,6 +221,12 @@ impl ResponderHandshake {
215221
}
216222
}
217223

224+
impl Default for ResponderHandshake {
225+
fn default() -> Self {
226+
Self::new()
227+
}
228+
}
229+
218230
#[instrument(skip(psk))]
219231
fn new_handshake(ciphersuite: Ciphersuite, psk: Psk, initiator: bool) -> HandshakeState {
220232
debug!("Creating handshake with PSK");

crates/bw-noise-protocol/src/transport.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ impl MultiDeviceTransport {
117117
recv_key,
118118
recv_rekey_counter: 1,
119119
seen_nonces: BTreeMap::new(),
120-
timeprovider: timeprovider,
120+
timeprovider,
121121
}
122122
}
123123

crates/bw-proxy/src/auth.rs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use rand::RngCore;
1313
use serde::{Deserialize, Serialize};
1414
use sha2::Digest;
1515

16+
use crate::error::ProxyError;
17+
1618
/// Signature algorithm selection for key generation.
1719
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1820
pub enum SignatureAlgorithm {
@@ -23,6 +25,7 @@ pub enum SignatureAlgorithm {
2325
MlDsa65,
2426
}
2527

28+
#[allow(clippy::derivable_impls)]
2629
impl Default for SignatureAlgorithm {
2730
fn default() -> Self {
2831
#[cfg(not(feature = "experimental-post-quantum-crypto"))]
@@ -38,6 +41,7 @@ impl Default for SignatureAlgorithm {
3841

3942
/// A cryptographic identity key-pair for signing challenges.
4043
#[derive(Clone)]
44+
#[allow(clippy::large_enum_variant)]
4145
pub enum IdentityKeyPair {
4246
Ed25519 {
4347
private_key_encoded: [u8; 32],
@@ -147,10 +151,13 @@ impl IdentityKeyPair {
147151
}
148152

149153
/// Deserialize a key pair from COSE key format.
150-
pub fn from_cose(cose_bytes: &[u8]) -> Result<Self, ()> {
151-
let cose_key = CoseKey::from_slice(cose_bytes).map_err(|_| ())?;
154+
pub fn from_cose(cose_bytes: &[u8]) -> Result<Self, ProxyError> {
155+
let cose_key = CoseKey::from_slice(cose_bytes)
156+
.map_err(|_| ProxyError::InvalidMessage("Invalid COSE key encoding".to_string()))?;
152157

153-
let alg = cose_key.alg.as_ref().ok_or(())?;
158+
let alg = cose_key.alg.as_ref().ok_or_else(|| {
159+
ProxyError::InvalidMessage("Missing algorithm in COSE key".to_string())
160+
})?;
154161

155162
match alg {
156163
coset::Algorithm::Assigned(iana::Algorithm::EdDSA) => {
@@ -168,7 +175,11 @@ impl IdentityKeyPair {
168175
}
169176
}
170177

171-
let seed = seed.ok_or(())?;
178+
let seed = seed.ok_or_else(|| {
179+
ProxyError::InvalidMessage(
180+
"Missing Ed25519 private key seed in COSE key".to_string(),
181+
)
182+
})?;
172183
let private_key = SigningKey::from_bytes(&seed);
173184
let public_key = VerifyingKey::from(&private_key);
174185

@@ -196,7 +207,11 @@ impl IdentityKeyPair {
196207
}
197208
}
198209

199-
let seed = seed.ok_or(())?;
210+
let seed = seed.ok_or_else(|| {
211+
ProxyError::InvalidMessage(
212+
"Missing ML-DSA-65 private key seed in COSE key".to_string(),
213+
)
214+
})?;
200215
let keypair = MlDsa65::key_gen_internal(&seed.into());
201216
let private_key = keypair.signing_key();
202217
let public_key = keypair.verifying_key();
@@ -207,7 +222,9 @@ impl IdentityKeyPair {
207222
public_key: public_key.clone(),
208223
})
209224
}
210-
_ => Err(()),
225+
_ => Err(ProxyError::InvalidMessage(
226+
"Unsupported algorithm in COSE key".to_string(),
227+
)),
211228
}
212229
}
213230

@@ -514,8 +531,10 @@ impl Challenge {
514531
.sign_deterministic(&self.0, &[])
515532
.expect("ML-DSA signing should succeed");
516533

517-
let mut header = coset::Header::default();
518-
header.alg = Some(coset::Algorithm::Assigned(iana::Algorithm::ML_DSA_65));
534+
let header = coset::Header {
535+
alg: Some(coset::Algorithm::Assigned(iana::Algorithm::ML_DSA_65)),
536+
..Default::default()
537+
};
519538

520539
let cose_sign1 = CoseSign1 {
521540
protected: coset::ProtectedHeader {

0 commit comments

Comments
 (0)