//! Utility functions // Adapted from 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. #![allow(non_camel_case_types, non_snake_case)] #![allow(clippy::missing_safety_doc, clippy::too_many_arguments)] use crate::{consts::*, error::ErrorKind, internal::*, yubikey::*}; use getrandom::getrandom; use hmac::Hmac; use libc::{calloc, free, memcpy, memmove, realloc, time}; use log::{error, warn}; use pbkdf2::pbkdf2; use sha1::Sha1; use std::ops::DerefMut; use std::{ffi::CString, mem, os::raw::c_void, ptr}; use zeroize::{Zeroize, Zeroizing}; /// Cardholder Unique Identifier (CHUID) Template /// /// Format defined in SP-800-73-4, Appendix A, Table 9 /// /// FASC-N containing S9999F9999F999999F0F1F0000000000300001E encoded in /// 4-bit BCD with 1 bit parity. run through the tools/fasc.pl script to get /// bytes. This CHUID has an expiry of 2030-01-01. /// /// Defined fields: /// /// - 0x30: FASC-N (hard-coded) /// - 0x34: Card UUID / GUID (settable) /// - 0x35: Exp. Date (hard-coded) /// - 0x3e: Signature (hard-coded, empty) /// - 0xfe: Error Detection Code (hard-coded) pub const CHUID_TMPL: &[u8] = &[ 0x30, 0x19, 0xd4, 0xe7, 0x39, 0xda, 0x73, 0x9c, 0xed, 0x39, 0xce, 0x73, 0x9d, 0x83, 0x68, 0x58, 0x21, 0x08, 0x42, 0x10, 0x84, 0x21, 0xc8, 0x42, 0x10, 0xc3, 0xeb, 0x34, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x08, 0x32, 0x30, 0x33, 0x30, 0x30, 0x31, 0x30, 0x31, 0x3e, 0x00, 0xfe, 0x00, ]; /// Cardholder Capability Container (CCC) Template /// /// f0: Card Identifier /// /// - 0xa000000116 == GSC-IS RID /// - 0xff == Manufacturer ID (dummy) /// - 0x02 == Card type (javaCard) /// - next 14 bytes: card ID pub static mut CCC_TMPL: &[u8] = &[ 0xf0, 0x15, 0xa0, 0x00, 0x00, 0x01, 0x16, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0x01, 0x21, 0xf2, 0x01, 0x21, 0xf3, 0x00, 0xf4, 0x01, 0x00, 0xf5, 0x01, 0x10, 0xf6, 0x00, 0xf7, 0x00, 0xfa, 0x00, 0xfb, 0x00, 0xfc, 0x00, 0xfd, 0x00, 0xfe, 0x00, ]; /// Card ID #[derive(Copy, Clone, Debug)] pub struct CardId([u8; 16]); /// Get Card ID pub unsafe fn ykpiv_util_get_cardid( state: &mut YubiKey, cardid: *mut CardId, ) -> Result<(), ErrorKind> { let mut buf = [0u8; CB_OBJ_MAX]; let mut len = buf.len(); let mut res = Ok(()); if cardid.is_null() { return Err(ErrorKind::GenericError); } _ykpiv_begin_transaction(state)?; if _ykpiv_ensure_application_selected(state).is_ok() { res = _ykpiv_fetch_object(state, YKPIV_OBJ_CHUID as i32, buf.as_mut_ptr(), &mut len); if res.is_ok() { if len != CHUID_TMPL.len() { res = Err(ErrorKind::GenericError); } else { memcpy( (*cardid).0.as_mut_ptr() as (*mut c_void), buf.as_mut_ptr().add(CHUID_GUID_OFFS) as (*const c_void), YKPIV_CARDID_SIZE, ); } } } let _ = _ykpiv_end_transaction(state); res } /// Set Card ID pub unsafe fn ykpiv_util_set_cardid( state: &mut YubiKey, cardid: *const CardId, ) -> Result<(), ErrorKind> { let mut id = [0u8; YKPIV_CARDID_SIZE]; let mut buf = [0u8; CHUID_TMPL.len()]; let mut res = Ok(()); if cardid.is_null() { getrandom(&mut id).map_err(|_| ErrorKind::RandomnessError)?; } else { memcpy( id.as_mut_ptr() as (*mut c_void), (*cardid).0.as_ptr() as (*const c_void), id.len(), ); } _ykpiv_begin_transaction(state)?; if _ykpiv_ensure_application_selected(state).is_ok() { memcpy( buf.as_mut_ptr() as *mut c_void, CHUID_TMPL.as_ptr() as *const c_void, buf.len(), ); memcpy( buf.as_mut_ptr().add(CHUID_GUID_OFFS) as *mut c_void, id.as_mut_ptr() as *const c_void, id.len(), ); res = _ykpiv_save_object( state, YKPIV_OBJ_CHUID as i32, buf.as_mut_ptr(), CHUID_TMPL.len(), ); } let _ = _ykpiv_end_transaction(state); res } /// Cardholder Capability Container (CCC) Identifier #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct CCCID([u8; 14]); /// Get Cardholder Capability Container (CCC) ID pub unsafe fn ykpiv_util_get_cccid(state: &mut YubiKey, ccc: *mut CCCID) -> Result<(), ErrorKind> { let mut res = Ok(()); let mut buf = [0u8; CB_OBJ_MAX]; let mut len = buf.len(); if ccc.is_null() { return Err(ErrorKind::GenericError); } _ykpiv_begin_transaction(state)?; if _ykpiv_ensure_application_selected(state).is_ok() { res = _ykpiv_fetch_object( state, YKPIV_OBJ_CAPABILITY as i32, buf.as_mut_ptr(), &mut len, ); if res.is_ok() { if len != CCC_TMPL.len() { let _ = _ykpiv_end_transaction(state); return Err(ErrorKind::GenericError); } memcpy( (*ccc).0.as_mut_ptr() as (*mut c_void), buf.as_mut_ptr().offset(9) as (*const c_void), YKPIV_CCCID_SIZE, ); } } res } /// Get Cardholder Capability Container (CCC) ID pub unsafe fn ykpiv_util_set_cccid( state: &mut YubiKey, ccc: *const CCCID, ) -> Result<(), ErrorKind> { let mut res = Ok(()); let mut id = [0u8; 14]; let mut buf = [0u8; 51]; let len: usize; if ccc.is_null() { getrandom(&mut id).map_err(|_| ErrorKind::RandomnessError)?; } else { memcpy( id.as_mut_ptr() as (*mut c_void), (*ccc).0.as_ptr() as (*const c_void), id.len(), ); } _ykpiv_begin_transaction(state)?; if _ykpiv_ensure_application_selected(state).is_ok() { len = 51; memcpy( buf.as_mut_ptr() as *mut c_void, CCC_TMPL.as_ptr() as *const c_void, len, ); memcpy( buf.as_mut_ptr().offset(9) as (*mut c_void), id.as_mut_ptr() as (*const c_void), 14, ); res = _ykpiv_save_object(state, YKPIV_OBJ_CAPABILITY as i32, buf.as_mut_ptr(), len); } let _ = _ykpiv_end_transaction(state); res } /// Get YubiKey device model pub unsafe fn ykpiv_util_devicemodel(state: &mut YubiKey) -> u32 { if state.context == 0 || state.context == -1 { DEVTYPE_UNKNOWN } else if state.is_neo { DEVTYPE_NEOr3 } else { DEVTYPE_YK4 } } /// Personal Identity Verification (PIV) keys #[derive(Copy, Clone, Debug)] pub struct YkPivKey { /// Card slot slot: u8, /// Length of cert cert_len: u16, /// Cert cert: [u8; 1], } /// Personal Identity Verification (PIV) key slots pub const SLOTS: [u8; 24] = [ YKPIV_KEY_AUTHENTICATION, YKPIV_KEY_SIGNATURE, YKPIV_KEY_KEYMGM, YKPIV_KEY_RETIRED1, YKPIV_KEY_RETIRED2, YKPIV_KEY_RETIRED3, YKPIV_KEY_RETIRED4, YKPIV_KEY_RETIRED5, YKPIV_KEY_RETIRED6, YKPIV_KEY_RETIRED7, YKPIV_KEY_RETIRED8, YKPIV_KEY_RETIRED9, YKPIV_KEY_RETIRED10, YKPIV_KEY_RETIRED11, YKPIV_KEY_RETIRED12, YKPIV_KEY_RETIRED13, YKPIV_KEY_RETIRED14, YKPIV_KEY_RETIRED15, YKPIV_KEY_RETIRED16, YKPIV_KEY_RETIRED17, YKPIV_KEY_RETIRED18, YKPIV_KEY_RETIRED19, YKPIV_KEY_RETIRED20, YKPIV_KEY_CARDAUTH, ]; /// List Personal Identity Verification (PIV) keys // TODO(tarcieri): fix clippy alignment warnings #[allow(clippy::cast_ptr_alignment)] pub unsafe fn ykpiv_util_list_keys( state: &mut YubiKey, key_count: *mut u8, data: *mut *mut YkPivKey, data_len: *mut usize, ) -> Result<(), ErrorKind> { let mut _currentBlock; let mut res = Ok(()); let mut p_key: *mut YkPivKey; let mut p_data: *mut u8 = ptr::null_mut(); let mut p_temp: *mut u8; let mut cb_data: usize; let mut offset: usize = 0; let mut buf = [0u8; YKPIV_OBJ_MAX_SIZE]; let mut cb_buf: usize; let mut i: usize; let mut cb_realloc: usize; let CB_PAGE: usize = 4096; if data.is_null() || data_len.is_null() || key_count.is_null() { return Err(ErrorKind::GenericError); } _ykpiv_begin_transaction(state)?; if _ykpiv_ensure_application_selected(state).is_ok() { *key_count = 0; *data = ptr::null_mut(); *data_len = 0; p_data = calloc(CB_PAGE, 1) as (*mut u8); if p_data.is_null() { let _ = _ykpiv_end_transaction(state); return Err(ErrorKind::MemoryError); } cb_data = CB_PAGE; i = 0; loop { if i >= mem::size_of::<*const u8>() { _currentBlock = 6; break; } cb_buf = buf.len(); res = _read_certificate(state, SLOTS[i], buf.as_mut_ptr(), &mut cb_buf); if res.is_ok() && (cb_buf > 0) { cb_realloc = if mem::size_of::() .wrapping_add(cb_buf) .wrapping_sub(1) > cb_data.wrapping_sub(offset) { (if mem::size_of::() .wrapping_add(cb_buf) .wrapping_sub(1) .wrapping_sub(cb_data.wrapping_sub(offset)) > CB_PAGE { mem::size_of::() .wrapping_add(cb_buf) .wrapping_sub(1) .wrapping_sub(cb_data.wrapping_sub(offset)) } else { CB_PAGE }) } else { 0 }; if cb_realloc != 0 { if { p_temp = realloc(p_data as (*mut c_void), cb_data.wrapping_add(cb_realloc)) as (*mut u8); p_temp } .is_null() { _currentBlock = 15; break; } p_data = p_temp; } cb_data = cb_data.wrapping_add(cb_realloc); p_key = p_data.add(offset) as *mut YkPivKey; (*p_key).slot = SLOTS[i]; (*p_key).cert_len = cb_buf as (u16); memcpy( (*p_key).cert.as_mut_ptr() as *mut c_void, buf.as_mut_ptr() as *const c_void, cb_buf, ); offset = offset.wrapping_add( mem::size_of::() .wrapping_add(cb_buf) .wrapping_sub(1), ); *key_count = (*key_count as i32 + 1) as u8; } i += 1; } if _currentBlock == 6 { *data = p_data as *mut YkPivKey; p_data = ptr::null_mut(); if !data_len.is_null() { *data_len = offset; } res = Ok(()); } else { res = Err(ErrorKind::MemoryError); } } if !p_data.is_null() { free(p_data as (*mut c_void)); } let _ = _ykpiv_end_transaction(state); res } /// Read certificate pub unsafe fn ykpiv_util_read_cert( state: &mut YubiKey, slot: u8, data: *mut *mut u8, data_len: *mut usize, ) -> Result<(), ErrorKind> { let mut res = Ok(()); let mut buf = [0u8; YKPIV_OBJ_MAX_SIZE]; let mut cb_buf: usize = buf.len(); if data.is_null() || data_len.is_null() { return Err(ErrorKind::GenericError); } _ykpiv_begin_transaction(state)?; if _ykpiv_ensure_application_selected(state).is_ok() { *data = ptr::null_mut(); *data_len = 0; res = _read_certificate(state, slot, buf.as_mut_ptr(), &mut cb_buf); if res.is_ok() { if cb_buf == 0 { *data = ptr::null_mut(); *data_len = 0; } else if { *data = calloc(cb_buf, 1) as *mut u8; *data } .is_null() { res = Err(ErrorKind::MemoryError); } else { memcpy( *data as (*mut c_void), buf.as_mut_ptr() as (*const c_void), cb_buf, ); *data_len = cb_buf; } } } let _ = _ykpiv_end_transaction(state); res } /// Write certificate pub unsafe fn ykpiv_util_write_cert( state: &mut YubiKey, slot: u8, data: *mut u8, data_len: usize, certinfo: u8, ) -> Result<(), ErrorKind> { let mut res = Ok(()); _ykpiv_begin_transaction(state)?; if _ykpiv_ensure_application_selected(state).is_ok() { res = _write_certificate(state, slot, data, data_len, certinfo); } let _ = _ykpiv_end_transaction(state); res } /// Delete certificate pub unsafe fn ykpiv_util_delete_cert(state: &mut YubiKey, slot: u8) -> Result<(), ErrorKind> { ykpiv_util_write_cert(state, slot, ptr::null_mut(), 0, 0) } /// Block PUK pub unsafe fn ykpiv_util_block_puk(state: &mut YubiKey) -> Result<(), ErrorKind> { let mut res = Ok(()); let mut puk = [0x30, 0x42, 0x41, 0x44, 0x46, 0x30, 0x30, 0x44]; let mut tries_remaining: i32 = -1; let mut data = [0u8; YKPIV_OBJ_MAX_SIZE]; let mut cb_data: usize = data.len(); let mut p_item: *mut u8 = ptr::null_mut(); let mut cb_item: usize = 0; let mut flags: u8 = 0; _ykpiv_begin_transaction(state)?; if _ykpiv_ensure_application_selected(state).is_err() { let _ = _ykpiv_end_transaction(state); return Ok(()); } while tries_remaining != 0 { res = ykpiv_change_puk(state, puk.as_ptr(), puk.len(), puk.as_ptr(), puk.len()); match res { Ok(()) => puk[0] += 1, Err(ErrorKind::WrongPin { tries }) => { tries_remaining = tries; continue; } Err(e) => { if e != ErrorKind::PinLocked { continue; } tries_remaining = 0; res = Ok(()); } } } if _read_metadata(state, TAG_ADMIN, data.as_mut_ptr(), &mut cb_data).is_ok() && _get_metadata_item( data.as_mut_ptr(), cb_data, TAG_ADMIN_FLAGS_1, &mut p_item, &mut cb_item, ) .is_ok() { if cb_item == mem::size_of_val(&flags) { // TODO(tarcieri): get rid of memcpy and pointers, replace with slices! #[allow(trivial_casts)] memcpy( &mut flags as *mut u8 as *mut c_void, p_item as (*const c_void), cb_item, ); } else { error!("admin flags exist, but are incorrect size = {}", cb_item); } } flags |= ADMIN_FLAGS_1_PUK_BLOCKED; if _set_metadata_item( data.as_mut_ptr(), &mut cb_data, CB_OBJ_MAX, TAG_ADMIN_FLAGS_1, &mut flags, 1, ) .is_ok() { if _write_metadata(state, TAG_ADMIN, data.as_mut_ptr(), cb_data).is_err() { error!("could not write admin metadata"); } } else { error!("could not set admin flags"); } let _ = _ykpiv_end_transaction(state); res } /// PIV container // TODO(tarcieri): dead code? #[allow(dead_code)] #[derive(Copy, Clone)] pub struct YkPivContainer { /// Name name: [i32; 40], /// Card slot slot: u8, /// Key spec key_spec: u8, /// Key size in bits key_size_bits: u16, /// Flags flags: u8, /// PIN ID pin_id: u8, /// Associated ECHD container associated_echd_container: u8, /// Cert fingerprint cert_fingerprint: [u8; 20], } /// Read mscmap pub unsafe fn ykpiv_util_read_mscmap( state: &mut YubiKey, containers: *mut *mut YkPivContainer, n_containers: *mut usize, ) -> Result<(), ErrorKind> { let mut res = Ok(()); let mut buf = [0u8; YKPIV_OBJ_MAX_SIZE]; let mut cb_buf: usize = buf.len(); let mut len: usize = 0; let mut ptr: *mut u8; if containers.is_null() || n_containers.is_null() { // TODO(str4d): Should this really continue on here? res = Err(ErrorKind::GenericError); } _ykpiv_begin_transaction(state)?; if _ykpiv_ensure_application_selected(state).is_ok() { *containers = ptr::null_mut(); *n_containers = 0; res = _ykpiv_fetch_object( state, YKPIV_OBJ_MSCMAP as i32, buf.as_mut_ptr(), &mut cb_buf, ); if res.is_err() { let _ = _ykpiv_end_transaction(state); return res; } ptr = buf.as_mut_ptr(); if cb_buf < CB_OBJ_TAG_MIN { let _ = _ykpiv_end_transaction(state); return Ok(()); } if *ptr == TAG_MSCMAP { ptr = ptr.add(1); ptr = ptr.add(_ykpiv_get_length(ptr, &mut len)); if len > cb_buf - (ptr as isize - buf.as_mut_ptr() as isize) as usize { let _ = _ykpiv_end_transaction(state); return Ok(()); } *containers = calloc(len, 1) as (*mut YkPivContainer); if (*containers).is_null() { res = Err(ErrorKind::MemoryError); } else { memcpy(*containers as (*mut c_void), ptr as (*const c_void), len); *n_containers = len.wrapping_div(mem::size_of::()); } } } res } /// Get max object size unsafe fn _obj_size_max(state: &mut YubiKey) -> usize { if state.is_neo { 2048 - 9 } else { CB_OBJ_MAX } } /// Write mscmap pub unsafe fn ykpiv_util_write_mscmap( state: &mut YubiKey, containers: *mut YkPivContainer, n_containers: usize, ) -> Result<(), ErrorKind> { let mut res = Ok(()); let mut buf = [0u8; CB_OBJ_MAX]; let mut offset: usize = 0; let data_len: usize = n_containers.wrapping_mul(mem::size_of::()); _ykpiv_begin_transaction(state)?; if _ykpiv_ensure_application_selected(state).is_ok() { if containers.is_null() || n_containers == 0 { if !containers.is_null() || n_containers != 0 { res = Err(ErrorKind::GenericError); } else { res = _ykpiv_save_object(state, YKPIV_OBJ_MSCMAP as i32, ptr::null_mut(), 0); } let _ = _ykpiv_end_transaction(state); return res; } let req_len = 1 + _ykpiv_set_length(buf.as_mut_ptr(), data_len) + data_len; if req_len > _obj_size_max(state) { let _ = _ykpiv_end_transaction(state); return Err(ErrorKind::SizeError); } buf[offset] = TAG_MSCMAP; offset += 1; offset += _ykpiv_set_length(buf.as_mut_ptr().add(offset), data_len); memcpy( buf.as_mut_ptr().add(offset) as (*mut c_void), containers as (*mut u8) as (*const c_void), data_len, ); offset = offset.wrapping_add(data_len); res = _ykpiv_save_object(state, YKPIV_OBJ_MSCMAP as i32, buf.as_mut_ptr(), offset); } let _ = _ykpiv_end_transaction(state); res } /// Read msroots pub unsafe fn ykpiv_util_read_msroots( state: &mut YubiKey, data: *mut *mut u8, data_len: *mut usize, ) -> Result<(), ErrorKind> { let mut _currentBlock = 0; let mut res; let mut buf = [0u8; YKPIV_OBJ_MAX_SIZE]; let mut cb_buf: usize; let mut len: usize = 0; let mut ptr: *mut u8; let mut tag: u8; let mut p_data: *mut u8; let mut p_temp: *mut u8; let mut cb_data: usize; let mut cb_realloc: usize; let mut offset: usize = 0; if data.is_null() || data_len.is_null() { return Err(ErrorKind::GenericError); } _ykpiv_begin_transaction(state)?; res = _ykpiv_ensure_application_selected(state); if res.is_err() { let _ = _ykpiv_end_transaction(state); return res; } *data = ptr::null_mut(); *data_len = 0; // allocate first page cb_data = _obj_size_max(state); p_data = calloc(cb_data, 1) as (*mut u8); if p_data.is_null() { let _ = _ykpiv_end_transaction(state); return Err(ErrorKind::MemoryError); } for object_id in YKPIV_OBJ_MSROOTS1..YKPIV_OBJ_MSROOTS5 { cb_buf = buf.len(); res = _ykpiv_fetch_object(state, object_id as i32, buf.as_mut_ptr(), &mut cb_buf); if res.is_err() { let _ = _ykpiv_end_transaction(state); return res; } ptr = buf.as_mut_ptr(); if cb_buf < CB_OBJ_TAG_MIN { let _ = _ykpiv_end_transaction(state); return Ok(()); } tag = *ptr; ptr = ptr.add(1); if tag != TAG_MSROOTS_MID && (tag != TAG_MSROOTS_END || object_id == YKPIV_OBJ_MSROOTS5) { // the current object doesn't contain a valid part of a msroots file let _ = _ykpiv_end_transaction(state); // treat condition as object isn't found return Ok(()); } ptr = ptr.add(_ykpiv_get_length(ptr, &mut len)); // check that decoded length represents object contents if len > cb_buf - (ptr as isize - buf.as_mut_ptr() as isize) as usize { let _ = _ykpiv_end_transaction(state); return Ok(()); } cb_realloc = if len > cb_data.wrapping_sub(offset) { len.wrapping_sub(cb_data.wrapping_sub(offset)) } else { 0 }; if cb_realloc != 0 { if { p_temp = realloc(p_data as (*mut c_void), cb_data.wrapping_add(cb_realloc)) as (*mut u8); p_temp } .is_null() { // realloc failed, pData will be freed in cleanup _currentBlock = 16; break; } p_data = p_temp; } cb_data = cb_data.wrapping_add(cb_realloc); memcpy( p_data.add(offset) as (*mut c_void), ptr as (*const c_void), len, ); offset = offset.wrapping_add(len); if tag == TAG_MSROOTS_END { _currentBlock = 15; break; } } if _currentBlock == 15 { *data = p_data; p_data = ptr::null_mut(); *data_len = offset; res = Ok(()); } else if _currentBlock == 16 { res = Err(ErrorKind::MemoryError); } else if _currentBlock != 21 { res = Ok(()); } if !p_data.is_null() { free(p_data as (*mut c_void)); } let _ = _ykpiv_end_transaction(state); res } /// Write msroots pub unsafe fn ykpiv_util_write_msroots( state: &mut YubiKey, data: *mut u8, data_len: usize, ) -> Result<(), ErrorKind> { let mut res = Ok(()); let mut buf = [0u8; CB_OBJ_MAX]; let mut offset: usize; let mut data_offset: usize = 0; let mut data_chunk: usize; let n_objs: usize; let cb_obj_max = _obj_size_max(state); _ykpiv_begin_transaction(state)?; if _ykpiv_ensure_application_selected(state).is_ok() { if data.is_null() || data_len == 0 { if !data.is_null() || data_len != 0 { res = Err(ErrorKind::GenericError); } else { res = _ykpiv_save_object(state, YKPIV_OBJ_MSROOTS1 as i32, ptr::null_mut(), 0); } let _ = _ykpiv_end_transaction(state); return res; } n_objs = (data_len / (cb_obj_max - 4)) + 1; if n_objs > 5 { let _ = _ykpiv_end_transaction(state); return Err(ErrorKind::SizeError); } for i in 0..n_objs { offset = 0; data_chunk = if cb_obj_max - 4 < data_len - data_offset { cb_obj_max - 4 } else { data_len - data_offset }; buf[offset] = if i == n_objs - 1 { TAG_MSROOTS_END } else { TAG_MSROOTS_MID }; offset += 1; offset += _ykpiv_set_length(buf.as_mut_ptr().add(offset), data_chunk); memcpy( buf.as_mut_ptr().add(offset) as *mut c_void, data.add(data_offset) as *const c_void, data_chunk, ); offset = offset.wrapping_add(data_chunk); res = _ykpiv_save_object( state, (YKPIV_OBJ_MSROOTS1 + i as u32) as i32, buf.as_mut_ptr(), offset, ); if res.is_err() { break; } data_offset = data_offset.wrapping_add(data_chunk); } } let _ = _ykpiv_end_transaction(state); res } // Keygen messages // TODO(tarcieri): extract these into an I18N-handling type? const SZ_SETTING_ROCA: &str = "Enable_Unsafe_Keygen_ROCA"; const SZ_ROCA_ALLOW_USER: &str = "was permitted by an end-user configuration setting, but is not recommended."; const SZ_ROCA_ALLOW_ADMIN: &str = "was permitted by an administrator configuration setting, but is not recommended."; const SZ_ROCA_BLOCK_USER: &str = "was blocked due to an end-user configuration setting."; const SZ_ROCA_BLOCK_ADMIN: &str = "was blocked due to an administrator configuration setting."; const SZ_ROCA_DEFAULT: &str = "was permitted by default, but is not recommended. The default behavior will change in a future Yubico release."; /// Generate key #[allow(clippy::cognitive_complexity)] pub unsafe fn ykpiv_util_generate_key( state: &mut YubiKey, slot: u8, algorithm: u8, pin_policy: u8, touch_policy: u8, modulus: *mut *mut u8, modulus_len: *mut usize, exp: *mut *mut u8, exp_len: *mut usize, point: *mut *mut u8, point_len: *mut usize, ) -> Result<(), ErrorKind> { let mut res = Ok(()); let mut in_data = [0u8; 11]; let mut in_ptr = in_data.as_mut_ptr(); let mut data = [0u8; 1024]; let mut templ = [0, YKPIV_INS_GENERATE_ASYMMETRIC, 0, 0]; let mut recv_len = data.len(); let mut sw: i32 = 0; let mut ptr_modulus: *mut u8 = ptr::null_mut(); let cb_modulus: usize; let mut ptr_exp: *mut u8 = ptr::null_mut(); let cb_exp: usize; let mut ptr_point: *mut u8 = ptr::null_mut(); let cb_point: usize; let setting_roca: SettingBool; if ykpiv_util_devicemodel(state) == DEVTYPE_YK4 && (algorithm == YKPIV_ALGO_RSA1024 || algorithm == YKPIV_ALGO_RSA2048) && state.ver.major == 4 && (state.ver.minor < 3 || state.ver.minor == 3 && (state.ver.patch < 5)) { let setting_name = CString::new(SZ_SETTING_ROCA).unwrap(); setting_roca = setting_get_bool(setting_name.as_ptr(), true); let psz_msg = match setting_roca.source { SettingSource::User => { if setting_roca.value { SZ_ROCA_ALLOW_USER } else { SZ_ROCA_BLOCK_USER } } SettingSource::Admin => { if setting_roca.value { SZ_ROCA_ALLOW_ADMIN } else { SZ_ROCA_BLOCK_ADMIN } } _ => SZ_ROCA_DEFAULT, }; warn!( "YubiKey serial number {} is affected by vulnerability CVE-2017-15361 \ (ROCA) and should be replaced. On-chip key generation {} See \ YSA-2017-01 \ for additional information on device replacement and mitigation assistance", state.serial, psz_msg ); if !setting_roca.value { return Err(ErrorKind::NotSupported); } } match algorithm { YKPIV_ALGO_RSA1024 | YKPIV_ALGO_RSA2048 => { if point.is_null() || point_len.is_null() { error!("invalid output parameter for ECC algorithm"); return Err(ErrorKind::GenericError); } *point = ptr::null_mut(); *point_len = 0; } YKPIV_ALGO_ECCP256 | YKPIV_ALGO_ECCP384 => { if modulus.is_null() || modulus_len.is_null() || exp.is_null() || exp_len.is_null() { error!("invalid output parameter for RSA algorithm"); return Err(ErrorKind::GenericError); } *modulus = ptr::null_mut(); *modulus_len = 0; *exp = ptr::null_mut(); *exp_len = 0; } _ => { error!("invalid algorithm specified"); return Err(ErrorKind::GenericError); } } _ykpiv_begin_transaction(state)?; if _ykpiv_ensure_application_selected(state).is_ok() { templ[3] = slot; *in_ptr = 0xac; *in_ptr.add(1) = 3; *in_ptr.add(2) = YKPIV_ALGO_TAG; *in_ptr.add(3) = 1; *in_ptr.add(4) = algorithm; in_ptr = in_ptr.add(5); if in_data[4] == 0 { res = Err(ErrorKind::AlgorithmError); error!("unexpected algorithm"); } else { if pin_policy != YKPIV_PINPOLICY_DEFAULT { in_data[1] += 3; *in_ptr = YKPIV_PINPOLICY_TAG; *in_ptr.add(1) = 1; *in_ptr.add(2) = pin_policy; in_ptr = in_ptr.add(3); } if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT { in_data[1] += 3; *in_ptr = YKPIV_TOUCHPOLICY_TAG; *in_ptr.add(1) = 1; *in_ptr.add(2) = touch_policy; in_ptr = in_ptr.add(3); } res = _ykpiv_transfer_data( state, templ.as_ptr(), in_data.as_mut_ptr(), in_ptr as isize - in_data.as_mut_ptr() as isize, data.as_mut_ptr(), &mut recv_len, &mut sw, ); if res.is_err() { error!("failed to communicate"); } else if sw != SW_SUCCESS { let err_msg = "failed to generate new key"; match sw { SW_ERR_INCORRECT_SLOT => { res = Err(ErrorKind::KeyError); error!("{} (incorrect slot)", err_msg); } SW_ERR_INCORRECT_PARAM => { res = Err(ErrorKind::AlgorithmError); if pin_policy != 0 { error!("{} (pin policy not supported?)", err_msg); } else if touch_policy != 0 { error!("{} (touch policy not supported?)", err_msg); } else { error!("{} (algorithm not supported?)", err_msg); } } SW_ERR_SECURITY_STATUS => { res = Err(ErrorKind::AuthenticationError); error!("{} (not authenticated)", err_msg); } _ => { res = Err(ErrorKind::GenericError); error!("{} (error {:x})", err_msg, sw); } } } else if algorithm == YKPIV_ALGO_RSA1024 || algorithm == YKPIV_ALGO_RSA2048 { let mut data_ptr: *mut u8 = data.as_mut_ptr().offset(5); let mut len: usize = 0; if *data_ptr != TAG_RSA_MODULUS { error!("Failed to parse public key structure (modulus)"); res = Err(ErrorKind::ParseError); } else { data_ptr = data_ptr.add(1); data_ptr = data_ptr.add(_ykpiv_get_length(data_ptr, &mut len)); cb_modulus = len; ptr_modulus = calloc(cb_modulus, 1) as *mut u8; if ptr_modulus.is_null() { error!("failed to allocate memory for modulus"); res = Err(ErrorKind::MemoryError); } else { memcpy( ptr_modulus as *mut c_void, data_ptr as *const c_void, cb_modulus, ); data_ptr = data_ptr.add(len); if *data_ptr != TAG_RSA_EXP { error!("failed to parse public key structure (public exponent)"); res = Err(ErrorKind::ParseError); } else { data_ptr = data_ptr.add(1); data_ptr = data_ptr.add(_ykpiv_get_length(data_ptr, &mut len)); cb_exp = len; ptr_exp = calloc(cb_exp, 1) as *mut u8; if ptr_exp.is_null() { error!("failed to allocate memory for public exponent"); res = Err(ErrorKind::MemoryError); } else { memcpy( ptr_exp as (*mut c_void), data_ptr as (*const c_void), cb_exp, ); // set output parameters *modulus = ptr_modulus; ptr_modulus = ptr::null_mut(); *modulus_len = cb_modulus; *exp = ptr_exp; ptr_exp = ptr::null_mut(); *exp_len = cb_exp; } } } } } else if algorithm == YKPIV_ALGO_ECCP256 || algorithm == YKPIV_ALGO_ECCP384 { let mut data_ptr: *mut u8 = data.as_mut_ptr().offset(3); let len = if algorithm == YKPIV_ALGO_ECCP256 { CB_ECC_POINTP256 } else { CB_ECC_POINTP384 }; let tag = *data_ptr; data_ptr = data_ptr.add(1); if tag != TAG_ECC_POINT { error!("failed to parse public key structure"); res = Err(ErrorKind::ParseError); } else { // the curve point should always be determined by the curve let len_byte = *data_ptr; data_ptr = data_ptr.add(1); if len_byte as usize != len { error!("unexpected length"); res = Err(ErrorKind::AlgorithmError); } else { cb_point = len; ptr_point = calloc(cb_point, 1) as (*mut u8); if ptr_point.is_null() { error!("failed to allocate memory for public point"); res = Err(ErrorKind::MemoryError); } else { memcpy( ptr_point as (*mut c_void), data_ptr as (*const c_void), cb_point, ); *point = ptr_point; ptr_point = ptr::null_mut(); *point_len = cb_point; } } } } else { error!("wrong algorithm"); res = Err(ErrorKind::AlgorithmError); } } } if !ptr_modulus.is_null() { free(modulus as (*mut c_void)); } if !ptr_exp.is_null() { free(ptr_exp as (*mut c_void)); } if !ptr_point.is_null() { free(ptr_exp as (*mut c_void)); } let _ = _ykpiv_end_transaction(state); res } /// Config mgm type #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(i32)] #[allow(non_camel_case_types)] pub enum YkPivConfigMgmType { /// Manual YKPIV_CONFIG_MGM_MANUAL = 0i32, /// Derived YKPIV_CONFIG_MGM_DERIVED = 1i32, /// Protected YKPIV_CONFIG_MGM_PROTECTED = 2i32, } /// Config #[derive(Copy, Clone)] pub struct YkPivConfig { /// Protected data available protected_data_available: bool, /// PUK blocked puk_blocked: bool, /// No block on upgrade puk_noblock_on_upgrade: bool, /// PIN last changed pin_last_changed: u32, /// MGM type mgm_type: YkPivConfigMgmType, } /// Get config pub unsafe fn ykpiv_util_get_config( state: &mut YubiKey, config: *mut YkPivConfig, ) -> Result<(), ErrorKind> { let mut data = [0u8; YKPIV_OBJ_MAX_SIZE]; let mut cb_data: usize = mem::size_of::<[u8; YKPIV_OBJ_MAX_SIZE]>(); let mut p_item: *mut u8 = ptr::null_mut(); let mut cb_item: usize = 0; let mut res = Ok(()); if config.is_null() { return Err(ErrorKind::GenericError); } (*config).protected_data_available = false; (*config).puk_blocked = false; (*config).puk_noblock_on_upgrade = false; (*config).pin_last_changed = 0; (*config).mgm_type = YkPivConfigMgmType::YKPIV_CONFIG_MGM_MANUAL; _ykpiv_begin_transaction(state)?; if _ykpiv_ensure_application_selected(state).is_ok() { if _read_metadata(state, TAG_ADMIN, data.as_mut_ptr(), &mut cb_data).is_ok() { if _get_metadata_item( data.as_mut_ptr(), cb_data, TAG_ADMIN_FLAGS_1, &mut p_item, &mut cb_item, ) .is_ok() { if *p_item & ADMIN_FLAGS_1_PUK_BLOCKED != 0 { (*config).puk_blocked = true; } if *p_item & ADMIN_FLAGS_1_PROTECTED_MGM != 0 { (*config).mgm_type = YkPivConfigMgmType::YKPIV_CONFIG_MGM_PROTECTED; } } if _get_metadata_item( data.as_mut_ptr(), cb_data, TAG_ADMIN_SALT, &mut p_item, &mut cb_item, ) .is_ok() { if (*config).mgm_type != YkPivConfigMgmType::YKPIV_CONFIG_MGM_MANUAL { error!("conflicting types of mgm key administration configured"); } else { (*config).mgm_type = YkPivConfigMgmType::YKPIV_CONFIG_MGM_DERIVED; } } if _get_metadata_item( data.as_mut_ptr(), cb_data, TAG_ADMIN_TIMESTAMP, &mut p_item, &mut cb_item, ) .is_ok() { if cb_item != CB_ADMIN_TIMESTAMP { error!("pin timestamp in admin metadata is an invalid size"); } else { // TODO(tarcieri): get rid of memcpy and pointers, replace with slices! #[allow(trivial_casts)] memcpy( &mut (*config).pin_last_changed as (*mut u32) as (*mut c_void), p_item as (*const c_void), cb_item, ); } } } cb_data = YKPIV_OBJ_MAX_SIZE; if _read_metadata(state, TAG_PROTECTED, data.as_mut_ptr(), &mut cb_data).is_ok() { (*config).protected_data_available = true; res = _get_metadata_item( data.as_mut_ptr(), cb_data, TAG_PROTECTED_FLAGS_1, &mut p_item, &mut cb_item, ); if res.is_ok() && *p_item & PROTECTED_FLAGS_1_PUK_NOBLOCK != 0 { (*config).puk_noblock_on_upgrade = true; } res = _get_metadata_item( data.as_mut_ptr(), cb_data, TAG_PROTECTED_MGM, &mut p_item, &mut cb_item, ); if res.is_ok() { if (*config).mgm_type != YkPivConfigMgmType::YKPIV_CONFIG_MGM_PROTECTED { error!("conflicting types of mgm key administration configured - protected mgm exists"); } (*config).mgm_type = YkPivConfigMgmType::YKPIV_CONFIG_MGM_PROTECTED; } } } let _ = _ykpiv_end_transaction(state); res } /// Set PIN last changed pub unsafe fn ykpiv_util_set_pin_last_changed(state: &mut YubiKey) -> Result<(), ErrorKind> { let mut data = [0u8; YKPIV_OBJ_MAX_SIZE]; let mut cb_data = data.len(); let mut res = Ok(()); _ykpiv_begin_transaction(state)?; if _ykpiv_ensure_application_selected(state).is_ok() { if _read_metadata(state, TAG_ADMIN, data.as_mut_ptr(), &mut cb_data).is_err() { cb_data = 0; } let mut tnow = time(ptr::null_mut()); res = { // TODO(tarcieri): get rid of memcpy and pointers, replace with slices! #[allow(trivial_casts)] _set_metadata_item( data.as_mut_ptr(), &mut cb_data, CB_OBJ_MAX, TAG_ADMIN_TIMESTAMP, &mut tnow as *mut i64 as *mut u8, CB_ADMIN_TIMESTAMP, ) }; if let Err(e) = &res { error!("could not set pin timestamp, err = {}", e); } else { res = _write_metadata(state, TAG_ADMIN, data.as_mut_ptr(), cb_data); if let Err(e) = &res { error!("could not write admin data, err = {}", e); } } } let _ = _ykpiv_end_transaction(state); res } /// Management key (MGM) #[derive(Clone)] pub struct YkPivMgm([u8; 24]); impl Zeroize for YkPivMgm { fn zeroize(&mut self) { self.0.zeroize(); } } impl Drop for YkPivMgm { fn drop(&mut self) { self.zeroize(); } } /// Get derived management key (MGM) pub unsafe fn ykpiv_util_get_derived_mgm( state: &mut YubiKey, pin: &[u8], mgm: &mut YkPivMgm, ) -> Result<(), ErrorKind> { let mut data = [0u8; YKPIV_OBJ_MAX_SIZE]; let mut cb_data: usize = data.len(); let mut p_item: *mut u8 = ptr::null_mut(); let mut cb_item: usize = 0; _ykpiv_begin_transaction(state)?; let mut res = _ykpiv_ensure_application_selected(state); if res.is_err() { let _ = _ykpiv_end_transaction(state); return res; } // recover management key res = _read_metadata(state, TAG_ADMIN, data.as_mut_ptr(), &mut cb_data); if res.is_ok() { res = _get_metadata_item( data.as_mut_ptr(), cb_data, TAG_ADMIN_SALT, &mut p_item, &mut cb_item, ); if res.is_ok() { if cb_item != CB_ADMIN_SALT { error!( "derived mgm salt exists, but is incorrect size = {}", cb_item, ); let _ = _ykpiv_end_transaction(state); return Err(ErrorKind::GenericError); } let salt = std::slice::from_raw_parts_mut(p_item, cb_item); pbkdf2::>(pin, &salt, ITER_MGM_PBKDF2, &mut (*mgm).0); } } let _ = _ykpiv_end_transaction(state); res } /// Get protected management key (MGM) pub unsafe fn ykpiv_util_get_protected_mgm( state: &mut YubiKey, mgm: *mut YkPivMgm, ) -> Result<(), ErrorKind> { // TODO(tarcieri): replace vec with wrapper type that impls `Zeroize` let mut data = Zeroizing::new([0u8; YKPIV_OBJ_MAX_SIZE].to_vec()); let mut cb_data: usize = data.len(); let mut p_item: *mut u8 = ptr::null_mut(); let mut cb_item: usize = 0; let mut res = Ok(()); if mgm.is_null() { return Err(ErrorKind::GenericError); } _ykpiv_begin_transaction(state)?; if _ykpiv_ensure_application_selected(state).is_ok() { res = _read_metadata(state, TAG_PROTECTED, data.as_mut_ptr(), &mut cb_data); if res.is_err() { error!("could not read protected data, err = {:?}", res); } else { res = _get_metadata_item( data.as_mut_ptr(), cb_data, TAG_PROTECTED_MGM, &mut p_item, &mut cb_item, ); if let Err(e) = &res { error!("could not read protected mgm from metadata, err = {}", e,); } else if cb_item != (*mgm).0.len() { error!( "protected data contains mgm, but is the wrong size = {}", cb_item, ); res = Err(ErrorKind::AuthenticationError); } else { memcpy( (*mgm).0.as_mut_ptr() as (*mut c_void), p_item as (*const c_void), cb_item, ); } } } let _ = _ykpiv_end_transaction(state); res } /// Set protected management key (MGM) /// /// To set a generated mgm, pass NULL for mgm, or set mgm.data to all zeroes #[allow(clippy::cognitive_complexity)] pub unsafe fn ykpiv_util_set_protected_mgm( state: &mut YubiKey, mgm: *mut YkPivMgm, ) -> Result<(), ErrorKind> { let mut f_generate: bool; let mut mgm_key = Zeroizing::new([0u8; 24]); // TODO(tarcieri): replace vec with wrapper type that impls `Zeroize` let mut data = Zeroizing::new([0u8; YKPIV_OBJ_MAX_SIZE].to_vec()); let mut cb_data = data.len(); let mut p_item: *mut u8 = ptr::null_mut(); let mut cb_item: usize = 0; let mut flags_1: u8 = 0; if mgm.is_null() { f_generate = true; } else { f_generate = true; memcpy( (*mgm_key).as_mut_ptr() as (*mut c_void), (*mgm).0.as_mut_ptr() as (*const c_void), (*mgm).0.len(), ); for i in 0..mgm_key.len() { if mgm_key[i] != 0 { f_generate = false; break; } } } _ykpiv_begin_transaction(state)?; if _ykpiv_ensure_application_selected(state).is_err() { let _ = _ykpiv_end_transaction(state); return Ok(()); } // try to set the mgm key as long as we don't encounter a fatal error loop { if f_generate { // generate a new mgm key if let Err(e) = getrandom(mgm_key.deref_mut()) { error!("could not generate new mgm, err = {}", e); let _ = _ykpiv_end_transaction(state); return Err(ErrorKind::RandomnessError); } } let ykrc = ykpiv_set_mgmkey(state, mgm_key.as_mut_ptr()); if ykrc.is_err() { // if set_mgmkey fails with KeyError, it means the generated key is weak // otherwise, log a warning, since the device mgm key is corrupt or we're in // a state where we can't set the mgm key if Err(ErrorKind::KeyError) != ykrc { error!( "could not set new derived mgm key, err = {}", ykrc.as_ref().unwrap_err() ); let _ = _ykpiv_end_transaction(state); return ykrc; } } else { f_generate = false; } if !f_generate { break; } } if !mgm.is_null() { memcpy( (*mgm).0.as_mut_ptr() as (*mut c_void), mgm_key.as_mut_ptr() as (*const c_void), (*mgm).0.len(), ); } // after this point, we've set the mgm key, so the function should // succeed, regardless of being able to set the metadata // set the new mgm key in protected data let mut ykrc = _read_metadata(state, TAG_PROTECTED, data.as_mut_ptr(), &mut cb_data); if ykrc.is_err() { // set current metadata blob size to zero, we'll add to the blank blob cb_data = 0; } ykrc = _set_metadata_item( data.as_mut_ptr(), &mut cb_data, CB_OBJ_MAX, TAG_PROTECTED_MGM, mgm_key.as_mut_ptr(), mgm_key.len(), ); if ykrc.is_err() { error!("could not set protected mgm item, err = {:?}", ykrc); } else { ykrc = _write_metadata(state, TAG_PROTECTED, data.as_mut_ptr(), cb_data); if ykrc.is_err() { error!("could not write protected data, err = {:?}", ykrc); let _ = _ykpiv_end_transaction(state); return ykrc; } } // set the protected mgm flag in admin data cb_data = YKPIV_OBJ_MAX_SIZE; ykrc = _read_metadata(state, TAG_ADMIN, data.as_mut_ptr(), &mut cb_data); if ykrc.is_err() { cb_data = 0; } else { ykrc = _get_metadata_item( data.as_mut_ptr(), cb_data, TAG_ADMIN_FLAGS_1, &mut p_item, &mut cb_item, ); if ykrc.is_err() { // flags are not set error!("admin data exists, but flags are not present",); } if cb_item == 1 { // TODO(tarcieri): get rid of memcpy and pointers, replace with slices! #[allow(trivial_casts)] memcpy( &mut flags_1 as (*mut u8) as (*mut c_void), p_item as (*const c_void), cb_item, ); } else { error!("admin data flags are an incorrect size = {}", cb_item,); } // remove any existing salt ykrc = _set_metadata_item( data.as_mut_ptr(), &mut cb_data, CB_OBJ_MAX, TAG_ADMIN_SALT, ptr::null_mut(), 0, ); if let Err(e) = &ykrc { error!("could not unset derived mgm salt, err = {}", e) } } flags_1 |= ADMIN_FLAGS_1_PROTECTED_MGM; ykrc = _set_metadata_item( data.as_mut_ptr(), &mut cb_data, CB_OBJ_MAX, TAG_ADMIN_FLAGS_1, &mut flags_1, mem::size_of_val(&flags_1), ); if let Err(e) = &ykrc { error!("could not set admin flags item, err = {}", e); } else { ykrc = _write_metadata(state, TAG_ADMIN, data.as_mut_ptr(), cb_data); if let Err(e) = ykrc.as_ref() { error!("could not write admin data, err = {}", e); } } let _ = _ykpiv_end_transaction(state); Ok(()) } /// Reset pub unsafe fn ykpiv_util_reset(state: &mut YubiKey) -> Result<(), ErrorKind> { let templ = [0, YKPIV_INS_RESET, 0, 0]; let mut data = [0u8; 255]; let mut recv_len = data.len(); let mut sw: i32 = 0; let res = ykpiv_transfer_data( state, templ.as_ptr(), ptr::null(), 0, data.as_mut_ptr(), &mut recv_len, &mut sw, ); match (res.is_ok(), sw) { (true, SW_SUCCESS) => Ok(()), _ => Err(ErrorKind::GenericError), } } /// Get object for slot pub fn ykpiv_util_slot_object(slot: u8) -> u32 { match slot { YKPIV_KEY_AUTHENTICATION => YKPIV_OBJ_AUTHENTICATION, YKPIV_KEY_SIGNATURE => YKPIV_OBJ_SIGNATURE, YKPIV_KEY_KEYMGM => YKPIV_OBJ_KEY_MANAGEMENT, YKPIV_KEY_CARDAUTH => YKPIV_OBJ_CARD_AUTH, YKPIV_KEY_ATTESTATION => YKPIV_OBJ_ATTESTATION, _ => { if slot >= YKPIV_KEY_RETIRED1 && (slot <= YKPIV_KEY_RETIRED20) { YKPIV_OBJ_RETIRED1 + (slot - YKPIV_KEY_RETIRED1) as u32 } else { 0 } } } } /// Read certificate unsafe fn _read_certificate( state: &mut YubiKey, slot: u8, buf: *mut u8, buf_len: *mut usize, ) -> Result<(), ErrorKind> { let mut ptr: *mut u8; let object_id = ykpiv_util_slot_object(slot) as i32; let mut len: usize = 0; if object_id == -1 { return Err(ErrorKind::InvalidObject); } if _ykpiv_fetch_object(state, object_id, buf, buf_len).is_ok() { ptr = buf; if *buf_len < CB_OBJ_TAG_MIN { *buf_len = 0; return Ok(()); } else if *{ let _old = ptr; ptr = ptr.offset(1); _old } == TAG_CERT { ptr = ptr.add(_ykpiv_get_length(ptr, &mut len)); if len > *buf_len - (ptr as isize - buf as isize) as usize { *buf_len = 0; return Ok(()); } else { memmove(buf as (*mut c_void), ptr as (*const c_void), len); *buf_len = len; } } } else { *buf_len = 0; } Ok(()) } /// Write certificate unsafe fn _write_certificate( state: &mut YubiKey, slot: u8, data: *mut u8, data_len: usize, certinfo: u8, ) -> Result<(), ErrorKind> { let mut buf = [0u8; CB_OBJ_MAX]; let object_id = ykpiv_util_slot_object(slot) as i32; let mut offset: usize = 0; let mut req_len: usize; if object_id == -1 { return Err(ErrorKind::InvalidObject); } if data.is_null() || data_len == 0 { if !data.is_null() || data_len != 0 { return Err(ErrorKind::GenericError); } return _ykpiv_save_object(state, object_id, ptr::null_mut(), 0); } req_len = 1 /* cert tag */ + 3 /* compression tag + data*/ + 2 /* lrc */; req_len += _ykpiv_set_length(buf.as_mut_ptr(), data_len); req_len += data_len; if req_len < data_len || req_len > _obj_size_max(state) { return Err(ErrorKind::SizeError); } buf[offset] = TAG_CERT; offset += 1; offset += _ykpiv_set_length(buf.as_mut_ptr().add(offset), data_len); memcpy( buf.as_mut_ptr().add(offset) as (*mut c_void), data as (*const c_void), data_len, ); offset += data_len; // write compression info and LRC trailer buf[offset] = TAG_CERT_COMPRESS; buf[offset + 1] = 0x01; buf[offset + 2] = if certinfo == YKPIV_CERTINFO_GZIP { 0x01 } else { 0x00 }; buf[offset + 3] = TAG_CERT_LRC; buf[offset + 4] = 00; offset += 5; _ykpiv_save_object(state, object_id, buf.as_mut_ptr(), offset) } /// Get metadata item unsafe fn _get_metadata_item( data: *mut u8, cb_data: usize, tag: u8, pp_item: *mut *mut u8, pcb_item: *mut usize, ) -> Result<(), ErrorKind> { let mut p_temp: *mut u8 = data; let mut cb_temp: usize = 0; let mut tag_temp: u8; if data.is_null() || pp_item.is_null() || pcb_item.is_null() { return Err(ErrorKind::GenericError); } *pp_item = ptr::null_mut(); *pcb_item = 0; while p_temp < data.add(cb_data) { tag_temp = *p_temp; p_temp = p_temp.add(1); if !_ykpiv_has_valid_length(p_temp, data.add(cb_data) as usize - p_temp as usize) { return Err(ErrorKind::SizeError); } p_temp = p_temp.add(_ykpiv_get_length(p_temp, &mut cb_temp)); if tag_temp == tag { break; } p_temp = p_temp.add(cb_temp); } if p_temp < data.add(cb_data) { *pp_item = p_temp; *pcb_item = cb_temp; Ok(()) } else { Err(ErrorKind::GenericError) } } /// Get length size fn _get_length_size(length: usize) -> i32 { if length < 0x80 { 1 } else if length < 0xff { 2 } else { 3 } } /// Set metadata item unsafe fn _set_metadata_item( data: *mut u8, pcb_data: *mut usize, cb_data_max: usize, tag: u8, p_item: *mut u8, cb_item: usize, ) -> Result<(), ErrorKind> { let mut p_temp: *mut u8 = data; let mut cb_temp: usize = 0; let mut tag_temp: u8 = 0; let mut cb_len: usize = 0; let p_next: *mut u8; let cb_moved: isize; if data.is_null() || pcb_data.is_null() { return Err(ErrorKind::GenericError); } while p_temp < data.add(*pcb_data) { tag_temp = *p_temp; p_temp = p_temp.add(1); cb_len = _ykpiv_get_length(p_temp, &mut cb_temp); p_temp = p_temp.add(cb_len); if tag_temp == tag { break; } p_temp = p_temp.add(cb_temp); } if tag_temp != tag { if cb_item == 0 { return Ok(()); } p_temp = data.add(*pcb_data); cb_len = _get_length_size(cb_item) as (usize); if (*pcb_data).wrapping_add(cb_len).wrapping_add(cb_item) > cb_data_max { return Err(ErrorKind::GenericError); } *p_temp = tag; p_temp = p_temp.add(1); p_temp = p_temp.add(_ykpiv_set_length(p_temp, cb_item)); memcpy(p_temp as (*mut c_void), p_item as (*const c_void), cb_item); *pcb_data += 1 + cb_len + cb_item; return Ok(()); } if cb_temp == cb_item { memcpy(p_temp as (*mut c_void), p_item as (*const c_void), cb_item); return Ok(()); } p_next = p_temp.add(cb_temp); cb_moved = cb_item as (isize) - cb_temp as (isize) + (if cb_item != 0 { _get_length_size(cb_item) } else { -1 } as (isize) - cb_len as (isize)); if (*pcb_data + cb_moved as usize) > cb_data_max { return Err(ErrorKind::GenericError); } memmove( p_next.offset(cb_moved) as (*mut c_void), p_next as (*const c_void), (*pcb_data).wrapping_sub( ((p_next as (isize)).wrapping_sub(data as (isize)) / mem::size_of::() as (isize)) as (usize), ), ); *pcb_data = (*pcb_data).wrapping_add(cb_moved as (usize)); if cb_item != 0 { p_temp = p_temp.offset(-(cb_len as (isize))); p_temp = p_temp.add(_ykpiv_set_length(p_temp, cb_item)); memcpy(p_temp as (*mut c_void), p_item as (*const c_void), cb_item); } Ok(()) } /// Read metadata unsafe fn _read_metadata( state: &mut YubiKey, tag: u8, data: *mut u8, pcb_data: *mut usize, ) -> Result<(), ErrorKind> { let mut p_temp: *mut u8; let mut cb_temp: usize; if data.is_null() || pcb_data.is_null() || YKPIV_OBJ_MAX_SIZE > *pcb_data { return Err(ErrorKind::GenericError); } let obj_id = match tag { TAG_ADMIN => YKPIV_OBJ_ADMIN_DATA, TAG_PROTECTED => YKPIV_OBJ_PRINTED, _ => return Err(ErrorKind::InvalidObject), } as i32; cb_temp = *pcb_data; *pcb_data = 0; _ykpiv_fetch_object(state, obj_id, data, &mut cb_temp)?; if cb_temp < CB_OBJ_TAG_MIN { return Err(ErrorKind::GenericError); } p_temp = data; if tag as (i32) != *{ let _old = p_temp; p_temp = p_temp.offset(1); _old } as (i32) { return Err(ErrorKind::GenericError); } p_temp = p_temp.add(_ykpiv_get_length(p_temp, pcb_data)); if *pcb_data > cb_temp - (p_temp as isize - data as isize) as usize { *pcb_data = 0; return Err(ErrorKind::GenericError); } memmove(data as (*mut c_void), p_temp as (*const c_void), *pcb_data); Ok(()) } /// Write metadata unsafe fn _write_metadata( state: &mut YubiKey, tag: u8, data: *mut u8, cb_data: usize, ) -> Result<(), ErrorKind> { let mut buf = [0u8; CB_OBJ_MAX]; // XXX REMEMBER TO ZERO let mut p_temp: *mut u8 = buf.as_mut_ptr(); if cb_data > _obj_size_max(state) - CB_OBJ_TAG_MAX { return Err(ErrorKind::GenericError); } let obj_id = match tag { TAG_ADMIN => YKPIV_OBJ_ADMIN_DATA, TAG_PROTECTED => YKPIV_OBJ_PRINTED, _ => return Err(ErrorKind::InvalidObject), } as i32; if data.is_null() || cb_data == 0 { return _ykpiv_save_object(state, obj_id, ptr::null_mut(), 0); } *{ let _old = p_temp; p_temp = p_temp.offset(1); _old } = tag; p_temp = p_temp.add(_ykpiv_set_length(p_temp, cb_data)); memcpy(p_temp as (*mut c_void), data as (*const c_void), cb_data); p_temp = p_temp.add(cb_data); _ykpiv_save_object( state, obj_id, buf.as_mut_ptr(), ((p_temp as (isize)).wrapping_sub(buf.as_mut_ptr() as (isize)) / mem::size_of::() as (isize)) as (usize), ) }