Libstirshaken
C library implementing STIR-shaken STI-SP AS/VS, STI-CA
Install / Use
/learn @signalwire/LibstirshakenREADME
Overview
STIR-Shaken is a technology for making secure calls by use of SSL certificates and JSON Web Tokens. For a general overview of the framwork please search web for: ATIS, "Signature-based Handling of Asserted Information using Tokens (SHAKEN). Governance Model and Certificate Management",
This library implements STIR (Secure Telephony Identity Revisited) and SHAKEN (Signature-based Handling of Asserted information using toKENs) (RFC8224, RFC8588), with X509 certificate path check (ATIS "Signature-based Handling of Asserted information using toKENs (SHAKEN)", RFC5280 "6. Certification Path Validation").
You can find a comprehensive list of specs relevant to Shaken at the bottom of this document.
libstirshaken
This library provides building blocks for implementing STIR-Shaken authentication and verification services, (STI-SP/AS, STI-SP/VS), as well as elements of STI-CA and STI-PA.
Interoperability
libstirshaken was tested for interoperability with other leading Shaken implementations (e.g. TransNexus).
Basic usage
Authentication
Create PASSporT using Authentication Service interface
stir_shaken_context_t ss = { 0 };
stir_shaken_passport_params_t params = {
.x5u = "https://shaken.signalwire.cloud/sp.pem"",
.attest = "B",
.desttn_key = "tn",
.desttn_val = "01256700800",
.iat = time(NULL),
.origtn_key = "tn",
.origtn_val = "01256500600",
.origid = "e32f4189-cb86-460f-bb92-bd3acb89f29c"
};
stir_shaken_passport_t *passport = NULL;
char *s = NULL, *sih = NULL;
stir_shaken_as_t *as = NULL;
stir_shaken_init(&ss, STIR_SHAKEN_LOGLEVEL_NOTHING);
as = stir_shaken_as_create(&ss);
stir_shaken_as_load_private_key(&ss, as, "sp.priv");
encoded = stir_shaken_as_authenticate_to_passport(&ss, as, ¶ms, &passport);
Print PASSporT in encoded form
printf("\n1. PASSporT encoded:\n%s\n", encoded);
1. PASSporT encoded:
eyJhbGciOiJFUzI1NiIsInBwdCI6InNoYWtlbiIsInR5cCI6InBhc3Nwb3J0IiwieDV1IjoiaHR0cHM6Ly9zaGFrZW4uc2lnbmFsd2lyZS5jbG91ZC9zcC5wZW0ifQ.eyJhdHRlc3QiOiJCIiwiZGVzdCI6eyJ0biI6WyIwMTI1NjcwMDgwMCJdfSwiaWF0IjoxNjE2NDQyNTIzLCJvcmlnIjp7InRuIjoiMDEyNTY1MDA2MDAifSwib3JpZ2lkIjoiZTMyZjQxODktY2I4Ni00NjBmLWJiOTItYmQzYWNiODlmMjljIn0.VT_KOQtrCS3WctNBFT7PKcUowTqHI1cZU3XhBaYEji8eH07XE5rYxomns1EnnePpw96zUF7cr-mBBro-wP65jg
Print PASSporT in decoded (plain) form
s = stir_shaken_passport_dump_str(&ss, passport, 1);
printf("\n2. PASSporT decoded:\n%s\n", s);
2. PASSporT decoded:
{
"alg": "ES256",
"ppt": "shaken",
"typ": "passport",
"x5u": "https://shaken.signalwire.cloud/sp.pem"
}
.
{
"attest": "B",
"dest": {
"tn": [
"01256700800"
]
},
"iat": 1616442523,
"orig": {
"tn": "01256500600"
},
"origid": "e32f4189-cb86-460f-bb92-bd3acb89f29c"
}
Create and print SIP Identity Header
sih = stir_shaken_as_authenticate_to_sih(&ss, as, ¶ms, &passport);
printf("\n3. SIP Identity Header:\n%s\n", sih);
3. SIP Identity Header:
eyJhbGciOiJFUzI1NiIsInBwdCI6InNoYWtlbiIsInR5cCI6InBhc3Nwb3J0IiwieDV1IjoiaHR0cHM6Ly9zaGFrZW4uc2lnbmFsd2lyZS5jbG91ZC9zcC5wZW0ifQ.eyJhdHRlc3QiOiJCIiwiZGVzdCI6eyJ0biI6WyIwMTI1NjcwMDgwMCJdfSwiaWF0IjoxNjE2NDQyNTIzLCJvcmlnIjp7InRuIjoiMDEyNTY1MDA2MDAifSwib3JpZ2lkIjoiZTMyZjQxODktY2I4Ni00NjBmLWJiOTItYmQzYWNiODlmMjljIn0.rN3n-2qjP9eVPMViBbK6sVUmN3tMRbI-8ffVs1M7J9KL0q0hMKtdZNBWj_TS5RkvakiDUoSErkDsahh2nRGD8Q;info=<https://shaken.signalwire.cloud/sp.pem>;alg=ES256;ppt=shaken
Verification
char *passport_encoded = "eyJhbGciOiJFUzI1NiIsInBwdCI6InNoYWtlbiIsInR5cCI6InBhc3Nwb3J0IiwieDV1IjoiaHR0cHM6Ly9zaGFrZW4uc2lnbmFsd2lyZS5jbG91ZC9zcC5wZW0ifQ.eyJhdHRlc3QiOiJCIiwiZGVzdCI6eyJ0biI6WyIwMTI1NjcwMDgwMCJdfSwiaWF0IjoxNjE2NDQyNTIzLCJvcmlnIjp7InRuIjoiMDEyNTY1MDA2MDAifSwib3JpZ2lkIjoiZTMyZjQxODktY2I4Ni00NjBmLWJiOTItYmQzYWNiODlmMjljIn0.VT_KOQtrCS3WctNBFT7PKcUowTqHI1cZU3XhBaYEji8eH07XE5rYxomns1EnnePpw96zUF7cr-mBBro-wP65jg";
char *sip_identity_header = "eyJhbGciOiJFUzI1NiIsInBwdCI6InNoYWtlbiIsInR5cCI6InBhc3Nwb3J0IiwieDV1IjoiaHR0cHM6Ly9zaGFrZW4uc2lnbmFsd2lyZS5jbG91ZC9zcC5wZW0ifQ.eyJhdHRlc3QiOiJCIiwiZGVzdCI6eyJ0biI6WyIwMTI1NjcwMDgwMCJdfSwiaWF0IjoxNjE2NDQyNTIzLCJvcmlnIjp7InRuIjoiMDEyNTY1MDA2MDAifSwib3JpZ2lkIjoiZTMyZjQxODktY2I4Ni00NjBmLWJiOTItYmQzYWNiODlmMjljIn0.rN3n-2qjP9eVPMViBbK6sVUmN3tMRbI-8ffVs1M7J9KL0q0hMKtdZNBWj_TS5RkvakiDUoSErkDsahh2nRGD8Q;info=<https://shaken.signalwire.cloud/sp.pem>;alg=ES256;ppt=shaken";
stir_shaken_passport_t *passport_out = NULL;
stir_shaken_cert_t *cert_out = NULL;
int iat_freshness_seconds = 60;
unsigned long connect_timeout_s = 5;
stir_shaken_vs_t *vs = NULL;
stir_shaken_init(&ss, STIR_SHAKEN_LOGLEVEL_NOTHING);
vs = stir_shaken_vs_create(&ss);
Optionally turn on X509 certificate path verification (and configure CA dir containing trusted CA root certificates)
stir_shaken_vs_set_x509_cert_path_check(&ss, vs, 1);
stir_shaken_vs_load_ca_dir(&ss, vs, "path/to/ca/dir");
Optionally set your own callback to supply certificates from cache
stir_shaken_vs_set_callback(&ss, vs, cache_callback);
Optionally set timeout on connect (default is 2 seconds)
stir_shaken_vs_set_connect_timeout(&ss, vs, connect_timeout_s);
Verify PASSporT's signature (involves certificate downloading or fetching from cache, and X509 cert path check if turned on), input is PASSporT encoded, output is status and optionally certificate and PASSporT used during verification
status = stir_shaken_vs_passport_verify(&ss, vs, passport_encoded, &cert_out, &passport_out);
if (STIR_SHAKEN_STATUS_OK != status) {
printf("PASSporT failed verification");
} else {
printf("PASSporT Verified");
}
Same as before, but input is PASSporT wrapped into SIP Identity Header
status = stir_shaken_vs_sih_verify(&ss, vs, sip_identity_header, &cert_out, &passport_out);
if (STIR_SHAKEN_STATUS_OK != status) {
printf("SIP Identity Header failed verification");
} else {
printf("SIP Identity Header verified");
}
Check that PASSporT applies to the current moment in time
if (STIR_SHAKEN_STATUS_OK != stir_shaken_passport_validate_iat_against_freshness(&ss, passport_out, iat_freshness_seconds)) {
error_description = stir_shaken_get_error(&ss, &error_code);
if (error_code == STIR_SHAKEN_ERROR_PASSPORT_INVALID_IAT_VALUE_FUTURE) {
printf("PASSporT not valid yet\n");
} else if (error_code == STIR_SHAKEN_ERROR_PASSPORT_INVALID_IAT_VALUE_EXPIRED) {
printf("PASSporT expired\n");
} else if (error_code == STIR_SHAKEN_ERROR_PASSPORT_INVALID_IAT) {
printf("PASSporT is missing @iat grant\n");
} else {
printf("You called this method with NULL PASSporT\n");
}
printf("PASSporT doesn't apply to the current moment in time\n");
printf("Error description is:\n%s\n", error_description);
}
Print PASSporT
passport_decoded = stir_shaken_passport_dump_str(&ss, passport, 1);
if (passport_decoded) {
printf("PASSporT is:\n%s\n", passport_decoded);
stir_shaken_free_jwt_str(passport_decoded);
passport_decoded = NULL;
}
PASSporT Verified
PASSporT is:
{
"alg": "ES256",
"ppt": "shaken",
"typ": "passport",
"x5u": "https://shaken.signalwire.cloud/sp.pem"
}
.
{
"attest": "B",
"dest": {
"tn": [
"01256700800"
]
},
"iat": 1616442523,
"orig": {
"tn": "01256500600"
},
"origid": "e32f4189-cb86-460f-bb92-bd3acb89f29c"
}
Print the certificate
if (STIR_SHAKEN_STATUS_OK == stir_shaken_read_cert_fields(&ss, cert)) {
printf("Certificate is:\n");
stir_shaken_print_cert_fields(stdout, cert);
}
Certificate is:
STIR-Shaken: STI Cert: Serial number: 01 1
STIR-Shaken: STI Cert: Issuer: /C=US/CN=SignalWire STI-CA Test
STIR-Shaken: STI Cert: Subject: /C=US/CN=SignalWire STI-SP Test
STIR-Shaken: STI Cert: Valid from: Aug 1 00:37:19 2020 GMT
STIR-Shaken: STI Cert: Valid to: Dec 17 00:37:19 2047 GMT
STIR-Shaken: STI Cert: Version: 3
Folders
/ - main folder
README.txt - this file
src/ - library sources
include/ - library headers
util/ - helper programs (stirshaken tool for running multiple commands, see below)
test/ - unit tests
examples/ - examples:
stir_shaken_as_basic.c - shows how Authentication Service may be constructed from the basic blocks
stir_shaken_as_easy.c - shows how to use default Authentication Service interface
stir_shaken_vs_basic.c - shows how Verification Service may be constructed from the basic blocks
stir_shaken_vs_easy.c - shows how to use default Verification Service interface
stir_shaken_ca.c - shows how Certificate Authority may be constructed from the basic blocks
stir_shaken_cert_req.c - shows how certificate may be requested and downloaded from CA
Compilation
Dependencies
CURL: https://github.com/curl/curl
OpenSSL: https://github.com/openssl/openssl version 1.1 or later
LibJWT: https://github.com/benmcollins/libjwt version 1.12 or later
LibKS: https://github.com/signalwire/libks
Signalwire Personal Access Token: https://freeswitch.org/confluence/display/FREESWITCH/HOWTO+Create+a+SignalWire+Personal+Access+Token
Packages for latest libks and libjwt which are required are available in the freeswitch package repositories:
Debian 10:
TOKEN=YOURSIGNALWIRETOKEN
apt-get update && apt-get install -y gnupg2 wget lsb-release
wget --http-user=signalwire --http-password=$TOKEN -O /usr/share/keyrings/signalwire-freeswitch-repo.gpg https://freeswitch.signalwire.com/repo/deb/debian-release/signalwire-freeswitch-repo.gpg
echo "machine freeswitch.signalwire.com login signalwire password $TOKEN" > /etc/apt/auth.conf
echo "deb [signed-by=/usr/share/keyrings/signalwire-freeswitch-repo.gpg] https://freeswitch.signalwire.com/repo/deb/debian-release/ `lsb_release -sc` main" > /etc/apt/sources.list.d/freeswitch.list
echo "deb-src [signed-by=/usr/share/keyrings/signalwire-freeswitch-repo.gpg] https://freeswitch.signalwire.com/repo/deb/debian-release/ `lsb_release -sc` main" >> /etc/apt/sources.list.d/freeswi
