Factor Response into apdu module; improved debugging

This commit merges the `apdu` and `response` modules: the responses are
APDU responses, and so the two are related.

This also moves the `trace` logging into the APDU type, which allows it
to display `Debug` output for APDUs and responses, which makes it easier
to understand what's going on (and will be even better once instructions
are converted into an enum so you can actually see what's happening).
This commit is contained in:
Tony Arcieri
2019-11-26 09:06:54 -08:00
parent 5fab09e54d
commit d3af2f2d80
8 changed files with 208 additions and 229 deletions
+1 -1
View File
@@ -149,7 +149,7 @@ jobs:
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: clippy command: clippy
args: -- -D warnings args: --all-features -- -D warnings
# TODO: use actions-rs/audit-check # TODO: use actions-rs/audit-check
security_audit: security_audit:
+6 -4
View File
@@ -112,15 +112,17 @@ To trace every message sent to/from the card i.e. the raw
Application Protocol Data Unit (APDU) messages, use the `trace` log level: Application Protocol Data Unit (APDU) messages, use the `trace` log level:
``` ```
running 1 test
[INFO yubikey_piv::yubikey] trying to connect to reader 'Yubico YubiKey OTP+FIDO+CCID' [INFO yubikey_piv::yubikey] trying to connect to reader 'Yubico YubiKey OTP+FIDO+CCID'
[INFO yubikey_piv::yubikey] connected to 'Yubico YubiKey OTP+FIDO+CCID' successfully [INFO yubikey_piv::yubikey] connected to 'Yubico YubiKey OTP+FIDO+CCID' successfully
[TRACE yubikey_piv::apdu] >>> APDU { cla: 0, ins: 164, p1: 4, p2: 0, lc: 5, data: [160, 0, 0, 3, 8] }
[TRACE yubikey_piv::transaction] >>> [0, 164, 4, 0, 5, 160, 0, 0, 3, 8] [TRACE yubikey_piv::transaction] >>> [0, 164, 4, 0, 5, 160, 0, 0, 3, 8]
[TRACE yubikey_piv::transaction] <<< [97, 17, 79, 6, 0, 0, 16, 0, 1, 0, 121, 7, 79, 5, 160, 0, 0, 3, 8, 144, 0] [TRACE yubikey_piv::apdu] <<< Response { status_words: Success, data: [97, 17, 79, 6, 0, 0, 16, 0, 1, 0, 121, 7, 79, 5, 160, 0, 0, 3, 8] }
[TRACE yubikey_piv::apdu] >>> APDU { cla: 0, ins: 253, p1: 0, p2: 0, lc: 0, data: [] }
[TRACE yubikey_piv::transaction] >>> [0, 253, 0, 0, 0] [TRACE yubikey_piv::transaction] >>> [0, 253, 0, 0, 0]
[TRACE yubikey_piv::transaction] <<< [5, 1, 2, 144, 0] [TRACE yubikey_piv::apdu] <<< Response { status_words: Success, data: [5, 1, 2] }
[TRACE yubikey_piv::apdu] >>> APDU { cla: 0, ins: 248, p1: 0, p2: 0, lc: 0, data: [] }
[TRACE yubikey_piv::transaction] >>> [0, 248, 0, 0, 0] [TRACE yubikey_piv::transaction] >>> [0, 248, 0, 0, 0]
[TRACE yubikey_piv::transaction] <<< [0, 115, 0, 178, 144, 0] [TRACE yubikey_piv::apdu] <<< Response { status_words: Success, data: [0, 115, 0, 178] }
test connect ... ok test connect ... ok
``` ```
+166 -4
View File
@@ -30,7 +30,8 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{error::Error, response::Response, transaction::Transaction, Buffer}; use crate::{error::Error, transaction::Transaction, Buffer};
use log::trace;
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
use zeroize::{Zeroize, Zeroizing}; use zeroize::{Zeroize, Zeroizing};
@@ -112,11 +113,13 @@ impl APDU {
/// Transmit this APDU using the given card transaction /// Transmit this APDU using the given card transaction
pub fn transmit(&self, txn: &Transaction<'_>, recv_len: usize) -> Result<Response, Error> { pub fn transmit(&self, txn: &Transaction<'_>, recv_len: usize) -> Result<Response, Error> {
let response_bytes = txn.transmit(&self.to_bytes(), recv_len)?; trace!(">>> {:?}", self);
Ok(Response::from_bytes(response_bytes)) let response = Response::from(txn.transmit(&self.to_bytes(), recv_len)?);
trace!("<<< {:?}", &response);
Ok(response)
} }
/// Consume this APDU and return a self-zeroizing buffer /// Serialize this APDU as a self-zeroizing byte buffer
pub fn to_bytes(&self) -> Buffer { pub fn to_bytes(&self) -> Buffer {
let mut bytes = Vec::with_capacity(5 + self.data.len()); let mut bytes = Vec::with_capacity(5 + self.data.len());
bytes.push(self.cla); bytes.push(self.cla);
@@ -159,3 +162,162 @@ impl Zeroize for APDU {
self.data.zeroize(); self.data.zeroize();
} }
} }
/// APDU responses
#[derive(Debug)]
pub(crate) struct Response {
/// Status words
status_words: StatusWords,
/// Buffer
data: Vec<u8>,
}
impl Response {
/// Create a new response from the given status words and buffer
#[cfg(feature = "untested")]
pub fn new(status_words: StatusWords, data: Vec<u8>) -> Response {
Response { status_words, data }
}
/// Get the [`StatusWords`] for this response.
pub fn status_words(&self) -> StatusWords {
self.status_words
}
/// Get the raw [`StatusWords`] code for this response.
#[cfg(feature = "untested")]
pub fn code(&self) -> u32 {
self.status_words.code()
}
/// Do the status words for this response indicate success?
pub fn is_success(&self) -> bool {
self.status_words.is_success()
}
/// Borrow the response data
pub fn data(&self) -> &[u8] {
self.data.as_ref()
}
}
impl AsRef<[u8]> for Response {
fn as_ref(&self) -> &[u8] {
self.data()
}
}
impl Drop for Response {
fn drop(&mut self) {
self.zeroize();
}
}
impl From<Vec<u8>> for Response {
fn from(mut bytes: Vec<u8>) -> Self {
if bytes.len() < 2 {
return Response {
status_words: StatusWords::None,
data: bytes,
};
}
let sw = StatusWords::from(
(bytes[bytes.len() - 2] as u32) << 8 | (bytes[bytes.len() - 1] as u32),
);
let len = bytes.len() - 2;
bytes.truncate(len);
Response {
status_words: sw,
data: bytes,
}
}
}
impl Zeroize for Response {
fn zeroize(&mut self) {
self.data.zeroize();
}
}
/// Status Words (SW) are 2-byte values returned by a card command.
///
/// The first byte of a status word is referred to as SW1 and the second byte
/// of a status word is referred to as SW2.
///
/// See NIST special publication 800-73-4, section 5.6:
/// <https://csrc.nist.gov/publications/detail/sp/800-73/4/final>
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum StatusWords {
/// No status words present in response
None,
/// Successful execution
Success,
/// Security status not satisfied
SecurityStatusError,
/// Authentication method blocked
AuthBlockedError,
/// Incorrect parameter in command data field
IncorrectParamError,
//
// Custom Yubico Status Word extensions
//
/// Incorrect card slot error
IncorrectSlotError,
/// Not supported error
NotSupportedError,
/// Other/unrecognized status words
Other(u32),
}
impl StatusWords {
/// Get the numerical response code for these status words
pub fn code(self) -> u32 {
match self {
StatusWords::None => 0,
StatusWords::SecurityStatusError => 0x6982,
StatusWords::AuthBlockedError => 0x6983,
StatusWords::IncorrectParamError => 0x6a80,
StatusWords::IncorrectSlotError => 0x6b00,
StatusWords::NotSupportedError => 0x6d00,
StatusWords::Success => 0x9000,
StatusWords::Other(n) => n,
}
}
/// Do these status words indicate success?
pub fn is_success(self) -> bool {
self == StatusWords::Success
}
}
impl From<u32> for StatusWords {
fn from(sw: u32) -> Self {
match sw {
0x0000 => StatusWords::None,
0x6982 => StatusWords::SecurityStatusError,
0x6983 => StatusWords::AuthBlockedError,
0x6a80 => StatusWords::IncorrectParamError,
0x6b00 => StatusWords::IncorrectSlotError,
0x6d00 => StatusWords::NotSupportedError,
0x9000 => StatusWords::Success,
_ => StatusWords::Other(sw),
}
}
}
impl From<StatusWords> for u32 {
fn from(sw: StatusWords) -> u32 {
sw.code()
}
}
+3 -3
View File
@@ -38,14 +38,14 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{ use crate::{
apdu::StatusWords,
certificate::{self, Certificate}, certificate::{self, Certificate},
consts::*, consts::*,
error::Error, error::Error,
response::StatusWords,
serialization::*, serialization::*,
settings, settings,
yubikey::YubiKey, yubikey::YubiKey,
AlgorithmId, ObjectId, AlgorithmId, Buffer, ObjectId,
}; };
use log::{debug, error, warn}; use log::{debug, error, warn};
@@ -309,7 +309,7 @@ pub fn generate(
} }
} }
let data = response.into_buffer(); let data = Buffer::new(response.data().into());
match algorithm { match algorithm {
YKPIV_ALGO_RSA1024 | YKPIV_ALGO_RSA2048 => { YKPIV_ALGO_RSA1024 | YKPIV_ALGO_RSA2048 => {
-1
View File
@@ -155,7 +155,6 @@ mod metadata;
pub mod mgm; pub mod mgm;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub mod msroots; pub mod msroots;
mod response;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
mod serialization; mod serialization;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
-186
View File
@@ -1,186 +0,0 @@
//! Responses to issued commands
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::Buffer;
/// Parsed response to a command
pub(crate) struct Response {
/// Status words
status_words: StatusWords,
/// Buffer
buffer: Buffer,
}
impl Response {
/// Parse a response from the given buffer
pub fn from_bytes(mut buffer: Buffer) -> Self {
if buffer.len() >= 2 {
let sw = StatusWords::from(
(buffer[buffer.len() - 2] as u32) << 8 | (buffer[buffer.len() - 1] as u32),
);
let len = buffer.len() - 2;
buffer.truncate(len);
Response {
status_words: sw,
buffer,
}
} else {
Response {
status_words: StatusWords::None,
buffer,
}
}
}
/// Create a new response from the given status words and buffer
#[cfg(feature = "untested")]
pub fn new(status_words: StatusWords, buffer: Buffer) -> Response {
Response {
status_words,
buffer,
}
}
/// Get the [`StatusWords`] for this response.
pub fn status_words(&self) -> StatusWords {
self.status_words
}
/// Get the raw [`StatusWords`] code for this response.
#[cfg(feature = "untested")]
pub fn code(&self) -> u32 {
self.status_words.code()
}
/// Do the status words for this response indicate success?
pub fn is_success(&self) -> bool {
self.status_words.is_success()
}
/// Borrow the response buffer
pub fn buffer(&self) -> &[u8] {
self.buffer.as_ref()
}
/// Consume this response, returning its buffer
#[cfg(feature = "untested")]
pub fn into_buffer(self) -> Buffer {
self.buffer
}
}
impl AsRef<[u8]> for Response {
fn as_ref(&self) -> &[u8] {
self.buffer()
}
}
/// Status Words (SW) are 2-byte values returned by a card command.
///
/// The first byte of a status word is referred to as SW1 and the second byte
/// of a status word is referred to as SW2.
///
/// See NIST special publication 800-73-4, section 5.6:
/// <https://csrc.nist.gov/publications/detail/sp/800-73/4/final>
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum StatusWords {
/// No status words present in response
None,
/// Successful execution
Success,
/// Security status not satisfied
SecurityStatusError,
/// Authentication method blocked
AuthBlockedError,
/// Incorrect parameter in command data field
IncorrectParamError,
//
// Custom Yubico Status Word extensions
//
/// Incorrect card slot error
IncorrectSlotError,
/// Not supported error
NotSupportedError,
/// Other/unrecognized status words
Other(u32),
}
impl StatusWords {
/// Get the numerical response code for these status words
pub fn code(self) -> u32 {
match self {
StatusWords::None => 0,
StatusWords::SecurityStatusError => 0x6982,
StatusWords::AuthBlockedError => 0x6983,
StatusWords::IncorrectParamError => 0x6a80,
StatusWords::IncorrectSlotError => 0x6b00,
StatusWords::NotSupportedError => 0x6d00,
StatusWords::Success => 0x9000,
StatusWords::Other(n) => n,
}
}
/// Do these status words indicate success?
pub fn is_success(self) -> bool {
self == StatusWords::Success
}
}
impl From<u32> for StatusWords {
fn from(sw: u32) -> Self {
match sw {
0x0000 => StatusWords::None,
0x6982 => StatusWords::SecurityStatusError,
0x6983 => StatusWords::AuthBlockedError,
0x6a80 => StatusWords::IncorrectParamError,
0x6b00 => StatusWords::IncorrectSlotError,
0x6d00 => StatusWords::NotSupportedError,
0x9000 => StatusWords::Success,
_ => StatusWords::Other(sw),
}
}
}
impl From<StatusWords> for u32 {
fn from(sw: StatusWords) -> u32 {
sw.code()
}
}
+21 -23
View File
@@ -1,17 +1,18 @@
//! YubiKey PC/SC transactions //! YubiKey PC/SC transactions
use crate::{apdu::APDU, consts::*, error::Error, yubikey::*, Buffer}; use crate::{apdu::APDU, consts::*, error::Error, yubikey::*};
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use crate::{ use crate::{
apdu::{Response, StatusWords},
mgm::MgmKey, mgm::MgmKey,
response::{Response, StatusWords},
serialization::*, serialization::*,
ObjectId, Buffer, ObjectId,
}; };
use log::{error, trace}; use log::{error, trace};
use std::convert::TryInto; use std::convert::TryInto;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use std::ptr; use std::ptr;
#[cfg(feature = "untested")]
use zeroize::Zeroizing; use zeroize::Zeroizing;
/// Exclusive transaction with the YubiKey's PC/SC card. /// Exclusive transaction with the YubiKey's PC/SC card.
@@ -43,10 +44,10 @@ impl<'tx> Transaction<'tx> {
/// single APDU messages at a time. For larger messages that need to be /// single APDU messages at a time. For larger messages that need to be
/// split into multiple APDUs, use the [`Transaction::transfer_data`] /// split into multiple APDUs, use the [`Transaction::transfer_data`]
/// method instead. /// method instead.
pub fn transmit(&self, send_buffer: &[u8], recv_len: usize) -> Result<Buffer, Error> { pub fn transmit(&self, send_buffer: &[u8], recv_len: usize) -> Result<Vec<u8>, Error> {
trace!(">>> {:?}", send_buffer); trace!(">>> {:?}", send_buffer);
let mut recv_buffer = Zeroizing::new(vec![0u8; recv_len]); let mut recv_buffer = vec![0u8; recv_len];
let len = self let len = self
.inner .inner
@@ -54,9 +55,6 @@ impl<'tx> Transaction<'tx> {
.len(); .len();
recv_buffer.truncate(len); recv_buffer.truncate(len);
trace!("<<< {:?}", recv_buffer.as_slice());
Ok(recv_buffer) Ok(recv_buffer)
} }
@@ -91,14 +89,14 @@ impl<'tx> Transaction<'tx> {
return Err(Error::GenericError); return Err(Error::GenericError);
} }
if response.buffer().len() < 3 { if response.data().len() < 3 {
return Err(Error::SizeError); return Err(Error::SizeError);
} }
Ok(Version { Ok(Version {
major: response.buffer()[0], major: response.data()[0],
minor: response.buffer()[1], minor: response.data()[1],
patch: response.buffer()[2], patch: response.data()[2],
}) })
} }
@@ -157,7 +155,7 @@ impl<'tx> Transaction<'tx> {
resp resp
}; };
response.buffer()[..4] response.data()[..4]
.try_into() .try_into()
.map(|serial| Serial::from(u32::from_be_bytes(serial))) .map(|serial| Serial::from(u32::from_be_bytes(serial)))
.map_err(|_| Error::SizeError) .map_err(|_| Error::SizeError)
@@ -363,7 +361,7 @@ impl<'tx> Transaction<'tx> {
} }
} }
let data = response.buffer(); let data = response.data();
// skip the first 7c tag // skip the first 7c tag
if data[0] != 0x7c { if data[0] != 0x7c {
@@ -404,7 +402,7 @@ impl<'tx> Transaction<'tx> {
max_out: usize, max_out: usize,
) -> Result<Response, Error> { ) -> Result<Response, Error> {
let mut in_offset = 0; let mut in_offset = 0;
let mut out_data = Zeroizing::new(vec![]); let mut out_data = vec![];
let mut sw = 0; let mut sw = 0;
loop { loop {
@@ -432,17 +430,17 @@ impl<'tx> Transaction<'tx> {
sw = response.status_words().code(); sw = response.status_words().code();
if out_data.len() - response.buffer().len() - 2 > max_out { if out_data.len() - response.data().len() - 2 > max_out {
error!( error!(
"output buffer too small: wanted to write {}, max was {}", "output buffer too small: wanted to write {}, max was {}",
out_data.len() - response.buffer().len() - 2, out_data.len() - response.data().len() - 2,
max_out max_out
); );
return Err(Error::SizeError); return Err(Error::SizeError);
} }
out_data.extend_from_slice(&response.buffer()[..response.buffer().len() - 2]); out_data.extend_from_slice(&response.data()[..response.data().len() - 2]);
in_offset += this_size; in_offset += this_size;
if in_offset >= in_data.len() { if in_offset >= in_data.len() {
@@ -460,20 +458,20 @@ impl<'tx> Transaction<'tx> {
sw = response.status_words().code(); sw = response.status_words().code();
if sw != StatusWords::Success.code() && (sw >> 8 != 0x61) { if sw != StatusWords::Success.code() && (sw >> 8 != 0x61) {
return Ok(Response::new(sw.into(), Zeroizing::new(vec![]))); return Ok(Response::new(sw.into(), vec![]));
} }
if out_data.len() + response.buffer().len() - 2 > max_out { if out_data.len() + response.data().len() - 2 > max_out {
error!( error!(
"output buffer too small: wanted to write {}, max was {}", "output buffer too small: wanted to write {}, max was {}",
out_data.len() + response.buffer().len() - 2, out_data.len() + response.data().len() - 2,
max_out max_out
); );
return Err(Error::SizeError); return Err(Error::SizeError);
} }
out_data.extend_from_slice(&response.buffer()[..response.buffer().len() - 2]); out_data.extend_from_slice(&response.data()[..response.data().len() - 2]);
} }
Ok(Response::new(sw.into(), out_data)) Ok(Response::new(sw.into(), out_data))
@@ -495,7 +493,7 @@ impl<'tx> Transaction<'tx> {
return Err(Error::GenericError); return Err(Error::GenericError);
} }
let data = response.into_buffer(); let data = Buffer::new(response.data().into());
let mut outlen = 0; let mut outlen = 0;
if data.len() < 2 || !has_valid_length(&data[1..], data.len() - 1) { if data.len() < 2 || !has_valid_length(&data[1..], data.len() - 1) {
+11 -7
View File
@@ -35,7 +35,11 @@
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use crate::{ use crate::{
apdu::APDU, key::SlotId, metadata, mgm::MgmKey, response::StatusWords, serialization::*, apdu::{StatusWords, APDU},
key::SlotId,
metadata,
mgm::MgmKey,
serialization::*,
ObjectId, ObjectId,
}; };
use crate::{consts::*, error::Error, transaction::Transaction, Buffer}; use crate::{consts::*, error::Error, transaction::Transaction, Buffer};
@@ -271,12 +275,12 @@ impl YubiKey {
.data(&[0x7c, 0x02, 0x80, 0x00]) .data(&[0x7c, 0x02, 0x80, 0x00])
.transmit(&txn, 261)?; .transmit(&txn, 261)?;
if !challenge.is_success() || challenge.buffer().len() < 12 { if !challenge.is_success() || challenge.data().len() < 12 {
return Err(Error::AuthenticationError); return Err(Error::AuthenticationError);
} }
// send a response to the cards challenge and a challenge of our own. // send a response to the cards challenge and a challenge of our own.
let response = mgm_key.decrypt(challenge.buffer()[4..12].try_into().unwrap()); let response = mgm_key.decrypt(challenge.data()[4..12].try_into().unwrap());
let mut data = [0u8; 22]; let mut data = [0u8; 22];
data[0] = 0x7c; data[0] = 0x7c;
@@ -308,7 +312,7 @@ impl YubiKey {
let response = mgm_key.encrypt(&challenge); let response = mgm_key.encrypt(&challenge);
use subtle::ConstantTimeEq; use subtle::ConstantTimeEq;
if response.ct_eq(&authentication.buffer()[4..12]).unwrap_u8() != 1 { if response.ct_eq(&authentication.data()[4..12]).unwrap_u8() != 1 {
return Err(Error::AuthenticationError); return Err(Error::AuthenticationError);
} }
@@ -803,11 +807,11 @@ impl YubiKey {
} }
} }
if response.buffer()[0] != 0x30 { if response.data()[0] != 0x30 {
return Err(Error::GenericError); return Err(Error::GenericError);
} }
Ok(response.into_buffer()) Ok(Buffer::new(response.data().into()))
} }
/// Get an auth challenge /// Get an auth challenge
@@ -824,7 +828,7 @@ impl YubiKey {
return Err(Error::AuthenticationError); return Err(Error::AuthenticationError);
} }
Ok(response.buffer()[4..12].try_into().unwrap()) Ok(response.data()[4..12].try_into().unwrap())
} }
/// Verify an auth response /// Verify an auth response