1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
|
mod ctor_migrate_util;
mod key_util;
use crate::hsc::ctor_migrate_util::{
CTOR_KEYSTORE_ID, CTOR_KEYSTORE1_PATH, CTOR_KEYSTORE2_PATH, CTOR_KEYSTORE3_PATH,
CTOR_KEYSTORE4_PATH, CTOR_KEYSTORE5_PATH, CTorMigrateCmd, ONION_ADDR_SERVICE_1,
ONION_ADDR_SERVICE_2,
};
use crate::hsc::key_util::{ADDR_LEN, ArtiHscKeyCmd, ONION_ADDR, build_hsc_key_cmd};
#[test]
fn gen_key() {
let state_dir = tempfile::TempDir::new().unwrap();
let state_dir = state_dir.path();
let mut cmd = build_hsc_key_cmd(ArtiHscKeyCmd::Get, state_dir);
cmd.write_stdin(ONION_ADDR);
let output = cmd.output().unwrap();
assert!(output.status.success());
assert!(
String::from_utf8(output.stdout)
.unwrap()
.contains("descriptor:x25519:")
);
let keystore_path = state_dir
.join("keystore/client")
.join(&ONION_ADDR[..ADDR_LEN]);
// Assert new private key has been generated
assert_eq!(
keystore_path
.read_dir()
.unwrap()
.flatten()
.next()
.unwrap()
.file_name(),
"ks_hsc_desc_enc.x25519_private"
);
}
#[test]
fn generate_then_rotate() {
let state_dir = tempfile::TempDir::new().unwrap();
let state_dir = state_dir.path();
let mut cmd = build_hsc_key_cmd(ArtiHscKeyCmd::Get, state_dir);
cmd.write_stdin(ONION_ADDR);
let output = cmd.output().unwrap();
assert!(output.status.success());
let descriptor = String::from_utf8(output.stdout).unwrap();
assert!(descriptor.contains("descriptor:x25519:"));
let mut cmd = build_hsc_key_cmd(ArtiHscKeyCmd::Rotate, state_dir);
cmd.write_stdin(ONION_ADDR);
let output = cmd.output().unwrap();
assert!(output.status.success());
let rotated_descriptor = String::from_utf8(output.stdout).unwrap();
assert!(rotated_descriptor.contains("descriptor:x25519:"));
// Assert key has been rotated
assert_ne!(descriptor, rotated_descriptor);
let keystore_path = state_dir
.join("keystore/client")
.join(&ONION_ADDR[..ADDR_LEN]);
// Assert new private key has been generated
assert_eq!(
keystore_path
.read_dir()
.unwrap()
.flatten()
.next()
.unwrap()
.file_name(),
"ks_hsc_desc_enc.x25519_private"
);
}
#[test]
fn generate_then_remove() {
let state_dir = tempfile::TempDir::new().unwrap();
let state_dir = state_dir.path();
let mut cmd = build_hsc_key_cmd(ArtiHscKeyCmd::Get, state_dir);
cmd.write_stdin(ONION_ADDR);
let output = cmd.output().unwrap();
assert!(output.status.success());
assert!(
String::from_utf8(output.stdout)
.unwrap()
.contains("descriptor:x25519:")
);
let mut cmd = build_hsc_key_cmd(ArtiHscKeyCmd::Remove, state_dir);
cmd.write_stdin(ONION_ADDR);
cmd.assert().success();
let keystore_path = state_dir
.join("keystore/client")
.join(&ONION_ADDR[..ADDR_LEN]);
let entries = keystore_path.read_dir().unwrap().flatten();
// Assert key has been removed
assert_eq!(entries.count(), 0);
}
/// Tests whether `ctor-migrate` can successfully migrate a valid C Tor client keystore
/// to an Arti keystore that does not already have discovery keys for the services
/// the C Tor client is configured with.
#[test]
fn simple_ctor_migration() {
let migrate_cmd = CTorMigrateCmd::new();
let assert_key_is_missing = |svc: &str| {
let err = migrate_cmd.keystore_contains_client_key(svc).unwrap_err();
assert!(err.to_string().contains(
"arti: error: Service discovery key not found. Rerun with --generate=if-needed to generate a new service discovery keypair"
));
};
// The client keystore doesn't have keys for either of the two services before the migration
assert_key_is_missing(ONION_ADDR_SERVICE_1);
assert_key_is_missing(ONION_ADDR_SERVICE_2);
let output = migrate_cmd.output(CTOR_KEYSTORE1_PATH).unwrap();
assert!(output.status.success());
assert!(
migrate_cmd
.keystore_contains_client_key(ONION_ADDR_SERVICE_1)
.is_ok()
);
assert!(
migrate_cmd
.keystore_contains_client_key(ONION_ADDR_SERVICE_2)
.is_ok()
);
}
/// Tests whether `ctor-migrate` fails if multiple client keys for the same service are present in
/// the C Tor keystore. A C Tor keystore must not contain multiple client keys for the same
/// service.
#[test]
fn migrate_duplicate_ctor_entries() {
let migrate_cmd = CTorMigrateCmd::new();
let output = migrate_cmd.output(CTOR_KEYSTORE2_PATH).unwrap();
assert!(!output.status.success());
assert!(
String::from_utf8(output.stderr)
.unwrap()
.contains("Invalid C Tor keystore (multiple keys exist for service")
);
}
/// Tests whether `ctor-migrate` succeeds when client keys for the same service are present in both
/// the Arti primary keystore and in the C Tor keystore being migrated. It also verifies that the
/// keys in the Arti primary keystore differ from the original ones after migration.
#[test]
fn forced_migration_overwrites_arti_keys() {
let migrate_cmd = CTorMigrateCmd::new();
migrate_cmd.populate_state_dir();
// Ensure the keystore is now populated with the expected keys
let output_service_1_prev = migrate_cmd
.keystore_contains_client_key(ONION_ADDR_SERVICE_1)
.unwrap();
let output_service_2_prev = migrate_cmd
.keystore_contains_client_key(ONION_ADDR_SERVICE_2)
.unwrap();
assert!(
migrate_cmd
.output(CTOR_KEYSTORE1_PATH)
.unwrap()
.status
.success()
);
// Check whether the current keys are different from the original ones
let output_service_1_current = migrate_cmd
.keystore_contains_client_key(ONION_ADDR_SERVICE_1)
.unwrap();
let output_service_2_current = migrate_cmd
.keystore_contains_client_key(ONION_ADDR_SERVICE_2)
.unwrap();
assert_ne!(output_service_1_prev, output_service_1_current);
assert_ne!(output_service_2_prev, output_service_2_current);
}
/// Tests that `ctor-migrate` fails when there are no valid entries in the
/// registered C Tor keystore, then test `ctor-migrate` fails when there
/// are no valid keys in the registered C Tor keystore.
#[test]
fn migrate_invalid_ctor_keystore() {
let assert_cmd_fails = |path: &str| {
let migrate_cmd = CTorMigrateCmd::new();
let output = migrate_cmd.output(path).unwrap();
assert!(!output.status.success());
let error = String::from_utf8(output.stderr).unwrap();
assert!(error.contains("No CTor client keys found in keystore"));
assert!(error.contains(CTOR_KEYSTORE_ID));
};
// NOTE: with CTOR_KEYSTORE3_PATH the keystore has a valid entry containing an invalid key.
// With CTOR_KEYSTORE4_PATH the keystore has an invalid entry containing a valid key.
// Both cases should fail.
assert_cmd_fails(CTOR_KEYSTORE3_PATH);
assert_cmd_fails(CTOR_KEYSTORE4_PATH);
}
/// Tests whether `ctor-migrate` succeeds when both valid and invalid entries are present in the
/// registered C Tor keystore.
#[test]
fn migrate_skips_invalid_ctor_entries() {
let migrate_cmd = CTorMigrateCmd::new();
let output = migrate_cmd.output(CTOR_KEYSTORE5_PATH).unwrap();
assert!(output.status.success());
assert!(
migrate_cmd
.keystore_contains_client_key(ONION_ADDR_SERVICE_1)
.is_ok()
);
}
|