ledger_bluetooth/
lib.rs

1/*******************************************************************************
2*  Licensed under the Apache License, Version 2.0 (the "License");
3*  you may not use this file except in compliance with the License.
4*  You may obtain a copy of the License at
5*
6*      http://www.apache.org/licenses/LICENSE-2.0
7*
8*  Unless required by applicable law or agreed to in writing, software
9*  distributed under the License is distributed on an "AS IS" BASIS,
10*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11*  See the License for the specific language governing permissions and
12*  limitations under the License.
13********************************************************************************/
14mod errors;
15use btleplug::api::{
16    Central, CharPropFlags, Characteristic, Manager, Peripheral, ScanFilter, WriteType,
17};
18pub use errors::LedgerBleError;
19
20use byteorder::BigEndian;
21
22use futures::StreamExt;
23use log::info;
24use uuid::Uuid;
25
26use std::collections::VecDeque;
27use std::ops::Deref;
28
29use ledger_transport::{async_trait, APDUAnswer, APDUCommand, Exchange};
30
31use btleplug::platform::{self, PeripheralId};
32use byteorder::WriteBytesExt;
33
34use std::time::Duration;
35use tokio::time;
36
37//  "9c332c11-e85b-3c29-536c-50c4d347ed4d"
38const NANO_X_UUID: Uuid = Uuid::from_fields(
39    0x9c332c11,
40    0xe85b,
41    0x3c29,
42    &[0x53, 0x6c, 0x50, 0xc4, 0xd3, 0x47, 0xed, 0x4d],
43);
44
45const MTU_COMMAND_TAG: u8 = 0x08;
46const APDU_COMMAND_TAG: u8 = 0x05;
47
48#[derive(Debug)]
49struct Request {
50    command_tag: u8,
51    payload: Vec<u8>,
52}
53
54#[derive(Debug)]
55struct InitialPacket {
56    command_tag: u8,
57    data_length: u16, // big endian
58    payload: Vec<u8>,
59}
60
61impl InitialPacket {
62    fn serialize(self) -> Vec<u8> {
63        let mut buf = Vec::with_capacity(self.payload.len() + 5);
64        buf.write_u8(self.command_tag).unwrap(); // Command Tag
65        buf.write_u16::<BigEndian>(0).unwrap(); // Packet Sequence Index
66        buf.write_u16::<BigEndian>(self.data_length).unwrap(); // Data Length
67        buf.extend(self.payload); // Payload
68        buf
69    }
70}
71
72impl TryFrom<Vec<u8>> for InitialPacket {
73    type Error = LedgerBleError;
74
75    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
76        if value.len() < 5 {
77            return Err(LedgerBleError::InvalidPacket);
78        }
79        let payload = if value.len() == 5 {
80            Vec::new()
81        } else {
82            value[5..].to_vec()
83        };
84        Ok(InitialPacket {
85            command_tag: value[0],
86            data_length: u16::from_be_bytes(value[3..5].try_into().unwrap()),
87            payload,
88        })
89    }
90}
91
92#[derive(Debug)]
93struct SubsequentPacket {
94    command_tag: u8,
95    _packet_sequence_index: u16, // big endian
96    payload: Vec<u8>,
97}
98
99impl SubsequentPacket {
100    fn serialize(self) -> Vec<u8> {
101        let mut buf = Vec::with_capacity(self.payload.len() + 3);
102        buf.write_u8(self.command_tag).unwrap(); // Command Tag
103        buf.write_u16::<BigEndian>(0).unwrap(); // Packet Sequence Index
104        buf.extend(self.payload); // Payload
105        buf
106    }
107}
108
109impl TryFrom<Vec<u8>> for SubsequentPacket {
110    type Error = LedgerBleError;
111
112    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
113        if value.len() < 3 {
114            return Err(LedgerBleError::InvalidPacket);
115        }
116        let payload = if value.len() == 3 {
117            Vec::new()
118        } else {
119            value[3..].to_vec()
120        };
121        Ok(SubsequentPacket {
122            command_tag: value[0],
123            _packet_sequence_index: u16::from_be_bytes(value[1..3].try_into().unwrap()),
124            payload,
125        })
126    }
127}
128
129#[derive(Debug)]
130pub struct TransportNativeBle {
131    device: platform::Peripheral,
132    write_characteristic: Characteristic,
133    notify_characteristic: Characteristic,
134}
135
136impl TransportNativeBle {
137    fn is_ledger(peripheral: &platform::Peripheral) -> bool {
138        peripheral.id() == PeripheralId::from(NANO_X_UUID)
139    }
140
141    /// Get a list of ledger devices available
142    pub async fn list_ledgers(
143        manager: &platform::Manager,
144    ) -> Result<Vec<platform::Peripheral>, LedgerBleError> {
145        let adapter_list = manager.adapters().await?;
146
147        for adapter in adapter_list.iter() {
148            adapter
149                .start_scan(ScanFilter::default())
150                .await
151                .expect("Can't scan BLE adapter for connected devices...");
152            time::sleep(Duration::from_secs(2)).await;
153            let peripherals = adapter.peripherals().await?;
154            if !peripherals.is_empty() {
155                return Ok(peripherals.into_iter().filter(Self::is_ledger).collect());
156            }
157        }
158        Ok(vec![])
159    }
160
161    /// Create a new BLE transport, returns a vector of available devices
162    pub async fn new(manager: &platform::Manager) -> Result<Vec<Self>, LedgerBleError> {
163        let ledgers = Self::list_ledgers(manager).await?;
164        if ledgers.is_empty() {
165            return Err(LedgerBleError::DeviceNotFound);
166        }
167
168        let mut output = Vec::new();
169        for ledger in ledgers {
170            if !ledger.is_connected().await? {
171                ledger.connect().await?;
172            }
173            ledger.discover_services().await?;
174
175            let write_characteristic = ledger
176                .characteristics()
177                .into_iter()
178                .find(|x| x.properties.contains(CharPropFlags::WRITE))
179                .unwrap();
180
181            let notify_characteristic = ledger
182                .characteristics()
183                .into_iter()
184                .find(|x| x.properties.contains(CharPropFlags::NOTIFY))
185                .unwrap();
186
187            output.push(TransportNativeBle {
188                device: ledger,
189                write_characteristic,
190                notify_characteristic,
191            });
192        }
193        Ok(output)
194    }
195
196    /// Create a new BLE transport, returns a vector of available devices
197    pub async fn connect(peripheral: platform::Peripheral) -> Result<Self, LedgerBleError> {
198        if !peripheral.is_connected().await? {
199            peripheral.connect().await?;
200        }
201        peripheral.discover_services().await?;
202
203        let write_characteristic = peripheral
204            .characteristics()
205            .into_iter()
206            .find(|x| x.properties.contains(CharPropFlags::WRITE))
207            .unwrap();
208
209        let notify_characteristic = peripheral
210            .characteristics()
211            .into_iter()
212            .find(|x| x.properties.contains(CharPropFlags::NOTIFY))
213            .unwrap();
214
215        Ok(TransportNativeBle {
216            device: peripheral,
217            write_characteristic,
218            notify_characteristic,
219        })
220    }
221
222    async fn write_request(&self, request: Request, mtu: u8) -> Result<(), LedgerBleError> {
223        // First we build the individual packets
224        let mut payloads = request
225            .payload
226            .chunks(mtu as usize)
227            .collect::<VecDeque<_>>();
228        let initial_packet = InitialPacket {
229            command_tag: request.command_tag,
230            data_length: request.payload.len() as u16,
231            payload: payloads.pop_front().unwrap_or_default().to_vec(),
232        };
233        let subsequent_packets = payloads
234            .iter()
235            .enumerate()
236            .map(|(i, payload)| SubsequentPacket {
237                command_tag: 0x03,
238                _packet_sequence_index: i as u16 + 1,
239                payload: payload.to_vec(),
240            })
241            .collect::<Vec<_>>();
242
243        // Then we subscribe to the notification channel
244        self.device.subscribe(&self.notify_characteristic).await?;
245
246        // Write the initial packet
247        info!("writing initial packet {:#?}", initial_packet);
248        self.device
249            .write(
250                &self.write_characteristic,
251                &initial_packet.serialize(),
252                WriteType::WithResponse,
253            )
254            .await?;
255
256        // Write the subsequent packets
257        for packet in subsequent_packets {
258            info!("writing subsequent packet {:#?}", packet);
259            self.device
260                .write(
261                    &self.write_characteristic,
262                    &packet.serialize(),
263                    WriteType::WithResponse,
264                )
265                .await?;
266        }
267
268        Ok(())
269    }
270
271    async fn read_response(&self) -> Result<Vec<u8>, LedgerBleError> {
272        let mut buffer = Vec::new();
273        let mut stream = self.device.notifications().await?;
274        let initial_packet: InitialPacket = stream.next().await.unwrap().value.try_into()?;
275        info!("reading initial packet {:#?}", initial_packet);
276        buffer.extend(initial_packet.payload);
277        while buffer.len() < initial_packet.data_length as usize {
278            let packet: SubsequentPacket = stream.next().await.unwrap().value.try_into()?;
279            info!("reading subsequent packet {:#?}", packet);
280            buffer.extend(packet.payload);
281        }
282        Ok(buffer)
283    }
284
285    async fn get_mtu(&self) -> Result<u8, LedgerBleError> {
286        let request = Request {
287            command_tag: MTU_COMMAND_TAG,
288            payload: Vec::new(),
289        };
290        self.write_request(request, 0xff).await?;
291        self.read_response().await.map(|x| x[0])
292    }
293
294    async fn write_apdu(&self, apdu_command: &[u8]) -> Result<(), LedgerBleError> {
295        let request = Request {
296            command_tag: APDU_COMMAND_TAG,
297            payload: apdu_command.to_vec(),
298        };
299        let mtu = Self::get_mtu(self).await?;
300        Self::write_request(self, request, mtu).await?;
301        Ok(())
302    }
303
304    pub async fn exchange<I: Deref<Target = [u8]>>(
305        &self,
306        command: &APDUCommand<I>,
307    ) -> Result<APDUAnswer<Vec<u8>>, LedgerBleError> {
308        self.write_apdu(&command.serialize()).await?;
309
310        let answer = self.read_response().await?;
311
312        APDUAnswer::from_answer(answer).map_err(|_| LedgerBleError::Comm("response was too short"))
313    }
314}
315
316#[async_trait]
317impl Exchange for TransportNativeBle {
318    type Error = LedgerBleError;
319    type AnswerType = Vec<u8>;
320
321    async fn exchange<I>(
322        &self,
323        command: &APDUCommand<I>,
324    ) -> Result<APDUAnswer<Self::AnswerType>, Self::Error>
325    where
326        I: Deref<Target = [u8]> + Send + Sync,
327    {
328        self.exchange(command).await
329    }
330}
331
332#[cfg(test)]
333mod integration_tests {
334    use crate::TransportNativeBle;
335    use btleplug::platform;
336    use byteorder::{LittleEndian, WriteBytesExt};
337    use ledger_transport::APDUCommand;
338    use log::{debug, info};
339
340    use serial_test::serial;
341
342    fn init_logging() {
343        let _ = env_logger::builder().is_test(true).try_init();
344    }
345
346    #[tokio::test]
347    #[serial]
348    async fn ledger_device_path() {
349        init_logging();
350        let manager = platform::Manager::new().await.unwrap();
351        let ledgers = TransportNativeBle::list_ledgers(&manager).await.unwrap();
352        for ledger in ledgers {
353            info!("{:#?}", ledger);
354        }
355    }
356
357    #[tokio::test]
358    #[serial]
359    async fn test_get_mtu() {
360        let manager = platform::Manager::new().await.unwrap();
361        let ledgers = TransportNativeBle::new(&manager).await.unwrap();
362        let mtu = ledgers[0].get_mtu().await.unwrap();
363        info!("mtu: {}", mtu);
364    }
365
366    #[tokio::test]
367    #[serial]
368    async fn test_get_version() {
369        let manager = platform::Manager::new().await.unwrap();
370        let ledger = TransportNativeBle::new(&manager)
371            .await
372            .unwrap()
373            .pop()
374            .unwrap();
375        let command = APDUCommand {
376            cla: 0x55,
377            ins: 0x00,
378            p1: 0x00,
379            p2: 0x00,
380            data: vec![],
381        };
382        let res = ledger.exchange(&command).await;
383
384        info!("res: {:#?}", res);
385    }
386
387    #[tokio::test]
388    #[serial]
389    async fn test_get_pub_key() {
390        let manager = platform::Manager::new().await.unwrap();
391        let ledger = TransportNativeBle::new(&manager)
392            .await
393            .unwrap()
394            .pop()
395            .unwrap();
396
397        let hrp = b"cosmos";
398        debug!("hrp: {:?}", hrp);
399
400        let mut get_addr_payload: Vec<u8> = Vec::new();
401        get_addr_payload.write_u8(hrp.len() as u8).unwrap(); // hrp len
402        get_addr_payload.extend(hrp); // hrp
403        get_addr_payload
404            .write_u32::<LittleEndian>(44 + 0x80000000)
405            .unwrap();
406        get_addr_payload
407            .write_u32::<LittleEndian>(118 + 0x80000000)
408            .unwrap();
409        get_addr_payload.write_u32::<LittleEndian>(0).unwrap();
410        get_addr_payload.write_u32::<LittleEndian>(0).unwrap();
411        get_addr_payload.write_u32::<LittleEndian>(0).unwrap();
412        let command = APDUCommand {
413            cla: 0x55,
414            ins: 0x04,
415            p1: 0x00,
416            p2: 0x00,
417            data: get_addr_payload,
418        };
419        let res = ledger.exchange(&command).await;
420
421        info!("res: {:#?}", res);
422    }
423}