The Portable Symmetric Key Container (PSKC) format is used to transport and provision symmetric keys to cryptographic devices or software. The PSKC Library allows you to parse, validate and generate PSKC data. The PSKC Library is written in C, uses LibXML, and is licensed under LGPLv2+. A companion to the library is a command line tool (pskctool) to interactively manipulate PSKC data.
To get a feeling of the PSKC data format we show the shortest possible valid PSKC content.
1 2 3 4 |
<?xml version="1.0"?> <KeyContainer xmlns="urn:ietf:params:xml:ns:keyprov:pskc" Version="1.0"> <KeyPackage/> </KeyContainer> |
Of course, since the intent with PSKC is to transport cryptographic keys, the example above is of little use since it does not carry any keys. The next example is more realistic, and show PSKC data used to transport a key used for a OATH HOTP implementation. The interesting values are the DeviceInfo values to identify the intended hardware, the Key Id "12345678", and the base64-encoded shared secret "MTIzNA==".
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<?xml version="1.0" encoding="UTF-8"?> <KeyContainer Version="1.0" xmlns="urn:ietf:params:xml:ns:keyprov:pskc"> <KeyPackage> <DeviceInfo> <Manufacturer>Manufacturer</Manufacturer> <SerialNo>987654321</SerialNo> </DeviceInfo> <Key Id="12345678" Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp"> <AlgorithmParameters> <ResponseFormat Length="8" Encoding="DECIMAL"/> </AlgorithmParameters> <Data> <Secret> <PlainValue>MTIzNDU2Nzg5MDEyMzQ1Njc4OTA= </PlainValue> </Secret> <Counter> <PlainValue>0</PlainValue> </Counter> </Data> </Key> </KeyPackage> </KeyContainer> |
To illustrate how the library works, let's give an example on how to parse the data above and print the device serial number (SerialNo field). The code below is complete and working but performs minimal error checking.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
#include <stdio.h> #include <pskc/pskc.h> /* * $ cc -o serialno serialno.c $(pkg-config --cflags --libs libpskc) * $ ./serialno pskc-hotp.xml * SerialNo: 987654321 * $ */ #define PSKC_CHECK_RC \ if (rc != PSKC_OK) { \ printf ("%s (%d): %s\n", pskc_strerror_name (rc), \ rc, pskc_strerror (rc)); \ return 1; \ } int main (int argc, const char *argv[]) { char buffer[4096]; FILE *fh = fopen (argv[1], "r"); size_t len = fread (buffer, 1, sizeof (buffer), fh); pskc_t *container; pskc_key_t *keypackage; int rc; fclose (fh); rc = pskc_global_init (); PSKC_CHECK_RC; rc = pskc_init (&container); PSKC_CHECK_RC; rc = pskc_parse_from_memory (container, len, buffer); PSKC_CHECK_RC; keypackage = pskc_get_keypackage (container, 0); if (keypackage) printf ("SerialNo: %s\n", pskc_get_device_serialno (keypackage)); pskc_done (container); pskc_global_done (); } |
Compiling and linking code with the PSKC Library requires that you specify correct compilation flags so that the header include file and the shared library is found. There is only one include file and it should be used like #include <pskc/pskc.h>. The library is called libpskc.so on GNU systems and libpskc.dll on Windows systems. To build the previous file, assuming the code is stored in a file called "serialno.c", invoke the following command.
1 |
cc -o serialno serialno.c -I/path/to/pskc/include/path -L/path/to/pskc/lib/path -Wl,-rpath/path/to/pskc/lib/path -lpskc |
A pkg-config file is provided, so that you may use pkg-config to select proper compilation flags if you want.
1 |
cc -o serialno serialno.c $(pkg-config --cflags --libs libpskc) |
After building the tool you would invoke it passing the name of the file with the PSKC input above, and it will print the serial number.
1 2 3 |
jas@latte:~$ ./serialno pskc.xml SerialNo: 987654321 jas@latte:~$ |
We conclude with a larger example illustrating how to read a PSKC file, parse it and print a human readable summary of the PSKC data to stderr, validate it against the PSKC XML Schema (this is normally not needed) and print the validation outcome to stderr, and iterate through all keys in the file and print to stdout a comma-separated list with three fields: the key id, the device serialno, and the hex encoded cryptographic key. This code example check error codes and releases resources.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <pskc/pskc.h> /* * $ cc -o pskc2csv pskc2csv.c $(pkg-config --cflags --libs libpskc) * $ ./pskc2csv pskc.xml 2> /dev/null * 12345678,12345678,MTIzNDU2Nzg5MDEyMzQ1Njc4OTA= * $ */ int main (int argc, const char *argv[]) { struct stat st; FILE *fh = NULL; char *buffer = NULL, *out; size_t i; pskc_t *container = NULL; pskc_key_t *keypackage; int exit_code = EXIT_FAILURE, rc, isvalid; rc = pskc_global_init (); if (rc != PSKC_OK) { fprintf (stderr, "pskc_global_init: %s\n", pskc_strerror (rc)); goto done; } if (argc != 2) { fprintf (stderr, "Usage: %s PSKCFILE\n", argv[0]); goto done; } /* Part 1: Read file. */ fh = fopen (argv[1], "r"); if (fh == NULL) { perror ("fopen"); goto done; } if (fstat (fileno (fh), &st) != 0) { perror ("fstat"); goto done; } buffer = malloc (st.st_size); if (buffer == NULL) { perror ("malloc"); goto done; } i = fread (buffer, 1, st.st_size, fh); if (i != st.st_size) { fprintf (stderr, "short read\n"); goto done; } /* Part 2: Parse PSKC data. */ rc = pskc_init (&container); if (rc != PSKC_OK) { fprintf (stderr, "pskc_init: %s\n", pskc_strerror (rc)); goto done; } rc = pskc_parse_from_memory (container, i, buffer); if (rc != PSKC_OK) { fprintf (stderr, "pskc_parse_from_memory: %s\n", pskc_strerror (rc)); goto done; } /* Part 3: Output human readable variant of PSKC data to stderr. */ rc = pskc_output (container, PSKC_OUTPUT_HUMAN_COMPLETE, &out, &i); if (rc != PSKC_OK) { fprintf (stderr, "pskc_output: %s\n", pskc_strerror (rc)); goto done; } fprintf (stderr, "%.*s\n", (int) i, out); pskc_free (out); /* Part 4: Validate PSKC data. */ rc = pskc_validate (container, &isvalid); if (rc != PSKC_OK) { fprintf (stderr, "pskc_validate: %s\n", pskc_strerror (rc)); goto done; } fprintf (stderr, "PSKC data is Schema valid: %s\n", isvalid ? "YES" : "NO"); /* Part 5: Iterate through keypackages and print key id, device serial number and base64 encoded secret. */ for (i = 0; (keypackage = pskc_get_keypackage (container, i)); i++) { const char *key_id = pskc_get_key_id (keypackage); const char *device_serialno = pskc_get_key_id (keypackage); const char *b64secret = pskc_get_key_data_b64secret (keypackage); printf ("%s,%s,%s\n", key_id ? key_id : "", device_serialno ? device_serialno : "", b64secret ? b64secret : ""); } exit_code = EXIT_SUCCESS; done: pskc_done (container); if (fh && fclose (fh) != 0) perror ("fclose"); free (buffer); pskc_global_done (); exit (exit_code); } |
Below we'll illustrate how to build the tool and run it on the same PSKC data as above. The tool prints different things to stdout and stderr, which you can see below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
jas@latte:~$ cc -o pskc2csv pskc2csv.c $(pkg-config --cflags --libs libpskc) jas@latte:~$ ./pskc2csv pskc.xml 2> /dev/null 12345678,12345678,MTIzNDU2Nzg5MDEyMzQ1Njc4OTA= jas@latte:~$ ./pskc2csv pskc.xml > /dev/null Portable Symmetric Key Container (PSKC): Version: 1.0 KeyPackage 0: DeviceInfo: Manufacturer: Manufacturer SerialNo: 987654321 Key: Id: 12345678 Algorithm: urn:ietf:params:xml:ns:keyprov:pskc:hotp Key Secret (base64): MTIzNDU2Nzg5MDEyMzQ1Njc4OTA= Key Counter: 0 Response Format Length: 8 Response Format Encoding: DECIMAL PSKC data is Schema valid: YES jas@latte:~$ |