Skip to content

Commit

Permalink
Compressed cert (#437)
Browse files Browse the repository at this point in the history
Add support for compressing and decompress certificates in the library and commanline tool
  • Loading branch information
aveenismail authored Nov 30, 2023
1 parent 43da2ee commit 563e0c3
Show file tree
Hide file tree
Showing 18 changed files with 340 additions and 195 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/linux_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Install prerequisites
run: |
set -x
sudo apt install libpcsclite-dev check gengetopt help2man openssl opensc
sudo apt install libpcsclite-dev check gengetopt help2man openssl opensc zlib1g-dev
- name: Build and install
run: |
Expand Down Expand Up @@ -130,7 +130,7 @@ jobs:
- name: Install prerequisites
run: |
set -x
sudo apt install libpcsclite-dev check gengetopt help2man openssl opensc
sudo apt install libpcsclite-dev check gengetopt help2man openssl opensc zlib1g-dev
- name: Build and install
run: |
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/linux_libressl_openssl1.0.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
- name: Install prerequisites
run: |
set -x
sudo apt install libpcsclite-dev check gengetopt help2man
sudo apt install libpcsclite-dev check gengetopt help2man zlib1g-dev
- name: Install LibreSSL from source
run: |
set -x
Expand Down Expand Up @@ -55,7 +55,7 @@ jobs:
- name: Install prerequisites
run: |
set -x
sudo apt install libpcsclite-dev check gengetopt help2man
sudo apt install libpcsclite-dev check gengetopt help2man zlib1g-dev
- name: Install OpenSSL 1.0 from source
run: |
Expand Down Expand Up @@ -99,7 +99,7 @@ jobs:
- name: Install prerequisites
run: |
set -x
sudo apt install libpcsclite-dev check gengetopt help2man
sudo apt install libpcsclite-dev check gengetopt help2man zlib1g-dev
- name: Install OpenSSL 1.1 from source
run: |
Expand Down Expand Up @@ -143,7 +143,7 @@ jobs:
- name: Install prerequisites
run: |
set -x
sudo apt install libpcsclite-dev check gengetopt help2man
sudo apt install libpcsclite-dev check gengetopt help2man zlib1g-dev
- name: Install OpenSSL 3.0 from source
run: |
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/windows_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Install prerequisites
run: |
set -x
sudo apt install libpcsclite-dev check gengetopt help2man
sudo apt install libpcsclite-dev check gengetopt help2man zlib1g-dev
- name: Create tar.gz
run: |
set -x
Expand Down Expand Up @@ -51,6 +51,7 @@ jobs:
vcpkg install openssl:x86-windows
vcpkg install getopt:x86-windows
vcpkg install check:x86-windows
vcpkg install zlib:x86-windows
- name: build for x86 architecture
run: |
Expand Down Expand Up @@ -127,6 +128,7 @@ jobs:
vcpkg install openssl:x64-windows
vcpkg install getopt:x64-windows
vcpkg install check:x64-windows
vcpkg install zlib:x64-windows
- name: build for x64 architecture
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/windows_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Install prerequisites
run: |
set -x
sudo apt install libpcsclite-dev check gengetopt help2man
sudo apt install libpcsclite-dev check gengetopt help2man zlib1g-dev
- name: Create tar.gz
run: |
set -x
Expand Down
3 changes: 3 additions & 0 deletions cmake/options.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,16 @@ option(SUPRESS_MSVC_WARNINGS "Suppresses a lot of the warnings when compiling wi
option(GENERATE_MAN_PAGES "Generate man pages for the command line tool" ON)
option(OPENSSL_STATIC_LINK "Statically link to OpenSSL" OFF)
option(ENABLE_COVERAGE "Enable/disable codecov evaluation" OFF)
option(ENABLE_CERT_COMPRESS "Enable/disable compression of certificate" ON)

set(YKCS11_DBG "0" CACHE STRING "Enable/disable YKCS11 debug messages. Possible values is 0 through 9")
set(BACKEND "check" CACHE STRING "use specific backend/linkage; 'pcsc', 'macscard' or'winscard'")
set(PCSC_LIB "" CACHE STRING "Name of custom PCSC lib")
set(PCSC_DIR "" CACHE STRING "Path to custom PCSC lib dir (use with PCSC_LIB")
set(GETOPT_LIB_DIR "" CACHE STRING "Path to look for getopt libraries")
set(GETOPT_INCLUDE_DIR "" CACHE STRING "Path to look for getopt.h file")
set(ZLIB_LIB_DIR "" CACHE STRING "Path to look for zlib libraries")
set(ZLIB_INCL_DIR "" CACHE STRING "Path to look for zlib.h file")
set(CHECK_PATH "" CACHE STRING "Path to look for 'check', the test framework for C. If 'check' is not found, tests are skipped")
set(OPENSSL_PKG_PATH "" CACHE STRING "Path to be prepended to 'PKG_CONFIG_PATH' evironment variable to look for libcrypto library")
set(PCSCLITE_PKG_PATH "" CACHE STRING "Path to be prepended to 'PKG_CONFIG_PATH' environment variable to look for pcsc-lite library")
Expand Down
2 changes: 1 addition & 1 deletion debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Maintainer: Debian Authentication Maintainers <[email protected]
Uploaders: Simon Josefsson <[email protected]>, Klas Lindfors <[email protected]>, Dain Nilsson <[email protected]>, Alessio Di Mauro <[email protected]>, Aveen Ismail <[email protected]>
Section: utils
Priority: extra
Build-Depends: debhelper (>= 9), pkg-config, libpcsclite-dev, libssl-dev, gengetopt, help2man, dh-autoreconf, chrpath, check, cmake
Build-Depends: debhelper (>= 9), pkg-config, libpcsclite-dev, libssl-dev, gengetopt, help2man, dh-autoreconf, chrpath, check, cmake, zlib1g-dev
Standards-Version: 4.1.4
Homepage: https://developers.yubico.com/yubico-piv-tool/

Expand Down
15 changes: 13 additions & 2 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,21 @@ include_directories(
${CMAKE_SOURCE_DIR}/common
)

if (ENABLE_CERT_COMPRESS)
add_definitions(-DUSE_CERT_COMPRESS="1")

find_library(ZLIB zlib PATHS ${ZLIB_LIB_DIR})
include_directories(${ZLIB_INCL_DIR})

find_package(ZLIB REQUIRED)

set(ZLIB_LIBS "ZLIB::ZLIB")
endif()

# static library
if(BUILD_STATIC_LIB)
add_library(ykpiv STATIC ${SOURCE})
target_link_libraries(ykpiv ${LIBCRYPTO_LIBRARIES} ${PCSC_LIBRARIES} ${PCSC_WIN_LIBS} ${PCSC_MACOSX_LIBS} ${PCSC_CUSTOM_LIBS})
target_link_libraries(ykpiv ${LIBCRYPTO_LIBRARIES} ${PCSC_LIBRARIES} ${PCSC_WIN_LIBS} ${PCSC_MACOSX_LIBS} ${PCSC_CUSTOM_LIBS} ${ZLIB_LIBS})
set_target_properties (ykpiv PROPERTIES COMPILE_FLAGS "-DSTATIC ")
if(WIN32)
set_target_properties(ykpiv PROPERTIES OUTPUT_NAME ykpiv_static)
Expand All @@ -61,7 +72,7 @@ endif(BUILD_STATIC_LIB)

# dynamic library
add_library(ykpiv_shared SHARED ${SOURCE})
target_link_libraries(ykpiv_shared ${LIBCRYPTO_LIBRARIES} ${PCSC_LIBRARIES} ${PCSC_WIN_LIBS} ${PCSC_MACOSX_LIBS} ${PCSC_CUSTOM_LIBS})
target_link_libraries(ykpiv_shared ${LIBCRYPTO_LIBRARIES} ${PCSC_LIBRARIES} ${PCSC_WIN_LIBS} ${PCSC_MACOSX_LIBS} ${PCSC_CUSTOM_LIBS} ${ZLIB_LIBS})
set_target_properties(ykpiv_shared PROPERTIES SOVERSION ${SO_VERSION} VERSION ${VERSION})
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set_target_properties(ykpiv_shared PROPERTIES INSTALL_RPATH "${YKPIV_INSTALL_LIB_DIR}")
Expand Down
171 changes: 130 additions & 41 deletions lib/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@
*
*/

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <ctype.h>
#include <time.h>

#include <zlib.h>

#include "internal.h"
#include "ykpiv.h"
#include "util.h"

#define MAX(a,b) (a) > (b) ? (a) : (b)
#define MIN(a,b) (a) < (b) ? (a) : (b)
Expand Down Expand Up @@ -415,7 +415,7 @@ ykpiv_rc ykpiv_util_write_cert(ykpiv_state *state, uint8_t slot, uint8_t *data,
}

ykpiv_rc ykpiv_util_delete_cert(ykpiv_state *state, uint8_t slot) {
return ykpiv_util_write_cert(state, slot, NULL, 0, 0);
return ykpiv_util_write_cert(state, slot, NULL, 0, YKPIV_CERTINFO_UNCOMPRESSED);
}

ykpiv_rc ykpiv_util_block_puk(ykpiv_state *state) {
Expand Down Expand Up @@ -1386,39 +1386,138 @@ uint32_t ykpiv_util_slot_object(uint8_t slot) {
return (uint32_t)object_id;
}

static ykpiv_rc _read_certificate(ykpiv_state *state, uint8_t slot, uint8_t *buf, size_t *buf_len) {
ykpiv_rc res = YKPIV_OK;
uint8_t *ptr = NULL;
unsigned long ul_len = (unsigned long)*buf_len;
int object_id = (int)ykpiv_util_slot_object(slot);
size_t offs, len = 0;
ykpiv_rc ykpiv_util_get_certdata(uint8_t *buf, size_t buf_len, uint8_t* certdata, unsigned long *certdata_len) {
uint8_t compress_info = YKPIV_CERTINFO_UNCOMPRESSED;
uint8_t *certptr = 0;
size_t cert_len = 0;
uint8_t *ptr = buf;

while (ptr < buf + buf_len) {
uint8_t tag = *ptr++;
size_t len = 0;
size_t offs = _ykpiv_get_length(ptr, buf + buf_len, &len);
if(!offs) {
DBG("Found invalid length for tag 0x%02x.", tag);
goto invalid_tlv;
}
ptr += offs; // move to after length bytes

switch (tag) {
case TAG_CERT:
certptr = ptr;
cert_len = len;
DBG("Found TAG_CERT with length %zu", cert_len);
break;
case TAG_CERT_COMPRESS:
if(len != 1) {
DBG("Found TAG_CERT_COMPRESS with invalid length %zu", len);
goto invalid_tlv;
}
compress_info = *ptr;
DBG("Found TAG_CERT_COMPRESS with length %zu value 0x%02x", len, compress_info);
break;
case TAG_CERT_LRC: {
// basically ignore it
DBG("Found TAG_CERT_LRC with length %zu", len);
break;
}
default:
DBG("Unknown cert tag 0x%02x", tag);
goto invalid_tlv;
break;
}
ptr += len; // move to after value bytes
}

invalid_tlv:
if(certptr == 0 || cert_len == 0 || ptr != buf + buf_len || compress_info > YKPIV_CERTINFO_GZIP) {
DBG("Invalid TLV encoding, treating as a raw certificate");
certptr = buf;
cert_len = buf_len;
}

if (compress_info == YKPIV_CERTINFO_GZIP) {
#ifdef USE_CERT_COMPRESS
z_stream zs;
zs.zalloc = Z_NULL;
zs.zfree = Z_NULL;
zs.opaque = Z_NULL;
zs.avail_in = (uInt) cert_len;
zs.next_in = (Bytef *) certptr;
zs.avail_out = (uInt) *certdata_len;
zs.next_out = (Bytef *) certdata;

if (inflateInit2(&zs, MAX_WBITS | 16) != Z_OK) {
DBG("Failed to initialize certificate decompression");
*certdata_len = 0;
return YKPIV_INVALID_OBJECT;
}

int res = inflate(&zs, Z_FINISH);
if (res != Z_STREAM_END) {
*certdata_len = 0;
if (res == Z_BUF_ERROR) {
DBG("Failed to decompress certificate. Allocated buffer is too small");
return YKPIV_SIZE_ERROR;
}
DBG("Failed to decompress certificate");
return YKPIV_INVALID_OBJECT;
}
if (inflateEnd(&zs) != Z_OK) {
DBG("Failed to finish certificate decompression");
*certdata_len = 0;
return YKPIV_INVALID_OBJECT;
}
*certdata_len = zs.total_out;
#else
DBG("Found compressed certificate. Decompressing certificate not supported");
*certdata_len = 0;
return YKPIV_PARSE_ERROR;
#endif
} else {
if (*certdata_len < cert_len) {
DBG("Buffer too small");
*certdata_len = 0;
return YKPIV_SIZE_ERROR;
}
memmove(certdata, certptr, cert_len);
*certdata_len = cert_len;
}
return YKPIV_OK;
}

if (-1 == object_id) return YKPIV_INVALID_OBJECT;
void ykpiv_util_write_certdata(uint8_t *rawdata, size_t rawdata_len, uint8_t compress_info, uint8_t* certdata, unsigned long *certdata_len) {
size_t offset = 0;

if (YKPIV_OK == (res = _ykpiv_fetch_object(state, object_id, buf, &ul_len))) {
ptr = buf;
unsigned long len_bytes = get_length_size(rawdata_len);
memmove(certdata + len_bytes + 1, rawdata, rawdata_len);

certdata[offset++] = TAG_CERT;
offset += _ykpiv_set_length(certdata+offset, rawdata_len);
offset += rawdata_len;
certdata[offset++] = TAG_CERT_COMPRESS;
certdata[offset++] = 1;
certdata[offset++] = compress_info;
certdata[offset++] = TAG_CERT_LRC;
certdata[offset++] = 0;
*certdata_len = offset;
}

// check that object contents are at least large enough to read the tag
if (ul_len < CB_OBJ_TAG_MIN) {
*buf_len = 0;
return YKPIV_OK;
}
static ykpiv_rc _read_certificate(ykpiv_state *state, uint8_t slot, uint8_t *buf, size_t *buf_len) {
ykpiv_rc res = YKPIV_OK;
int object_id = (int)ykpiv_util_slot_object(slot);

// check that first byte indicates "certificate" type
if (-1 == object_id) return YKPIV_INVALID_OBJECT;

if (*ptr++ == TAG_CERT) {
offs = _ykpiv_get_length(ptr, buf + ul_len, &len);
if(!offs) {
*buf_len = 0;
return YKPIV_OK;
}
ptr += offs;
unsigned char data[YKPIV_OBJ_MAX_SIZE * 10] = {0};
unsigned long data_len = sizeof (data);

memmove(buf, ptr, len);
*buf_len = len;
if (YKPIV_OK == (res = _ykpiv_fetch_object(state, object_id, data, &data_len))) {
if ((res = ykpiv_util_get_certdata(data, data_len, buf, buf_len)) != YKPIV_OK) {
DBG("Failed to get certificate data");
return res;
}
}
else {
} else {
*buf_len = 0;
}

Expand Down Expand Up @@ -1455,17 +1554,7 @@ static ykpiv_rc _write_certificate(ykpiv_state *state, uint8_t slot, uint8_t *da
if (req_len < data_len) return YKPIV_SIZE_ERROR; /* detect overflow of unsigned size_t */
if (req_len > _obj_size_max(state)) return YKPIV_SIZE_ERROR; /* obj_size_max includes limits for TLV encoding */

buf[offset++] = TAG_CERT;
offset += _ykpiv_set_length(buf + offset, data_len);
memcpy(buf + offset, data, data_len);
offset += data_len;

// write compression info and LRC trailer
buf[offset++] = TAG_CERT_COMPRESS;
buf[offset++] = 0x01;
buf[offset++] = certinfo == YKPIV_CERTINFO_GZIP ? 0x01 : 0x00;
buf[offset++] = TAG_CERT_LRC;
buf[offset++] = 00;
ykpiv_util_write_certdata(data, data_len, certinfo, buf, &offset);

// write onto device
return _ykpiv_save_object(state, object_id, buf, offset);
Expand Down
2 changes: 2 additions & 0 deletions lib/ykpiv.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
#include <stdint.h>
#include <ctype.h>

#include <zlib.h>

#include "internal.h"
#include "ykpiv.h"

Expand Down
23 changes: 23 additions & 0 deletions lib/ykpiv.h
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,29 @@ extern "C"
*/
ykpiv_rc ykpiv_util_read_cert(ykpiv_state *state, uint8_t slot, uint8_t **data, size_t *data_len);

/**
* Decompresses a certificate if it was compressed
*
* @param buf Fetched certificate data
* @param buf_len Length of fetched certificate data
* @param certdata Raw certificate bytes
* @param certdata_len Length of raw certificate bytes
*
* @return Error code
*/
ykpiv_rc ykpiv_util_get_certdata(uint8_t *buf, size_t buf_len, uint8_t* certdata, unsigned long *certdata_len);

/**
* Construct cert data to store
*
* @param data Raw certificate data
* @param data_len Length of raw certificate data
* @param compress_info Certificate compression state
* @param certdata Constructed certificate data
* @param certdata_len Length of constructed certificate data
*/
void ykpiv_util_write_certdata(uint8_t *data, size_t data_len, uint8_t compress_info, uint8_t* certdata, unsigned long *certdata_len);

/**
* Write a certificate to a given slot
*
Expand Down
Loading

0 comments on commit 563e0c3

Please sign in to comment.