Skip to content

Commit c337f91

Browse files
Copilotdjw8605
andcommitted
Add comprehensive tests and documentation for offline cache functionality
Co-authored-by: djw8605 <79268+djw8605@users.noreply.github.com>
1 parent 6d369a9 commit c337f91

File tree

3 files changed

+199
-0
lines changed

3 files changed

+199
-0
lines changed

OFFLINE_CACHE.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# SciTokens Offline Cache Support
2+
3+
This document describes the offline cache functionality added to the scitokens-cpp library.
4+
5+
## Overview
6+
7+
The scitokens library now supports offline operation through a direct SQLite cache file that can be pre-populated with JWKS data. This enables environments where external network access to fetch public keys is not available or desired.
8+
9+
## New Features
10+
11+
### 1. SCITOKENS_KEYCACHE_FILE Environment Variable
12+
13+
When set, this environment variable points directly to a SQLite database file that will be used for the key cache, bypassing the normal cache location resolution (XDG_CACHE_HOME, ~/.cache, etc.).
14+
15+
```bash
16+
export SCITOKENS_KEYCACHE_FILE=/path/to/offline.db
17+
```
18+
19+
### 2. scitokens-keycache Command Line Tool
20+
21+
A new command-line utility for creating and managing offline cache files.
22+
23+
#### Usage
24+
```bash
25+
scitokens-keycache --cache-file <cache_file> --jwks <jwks_file> --issuer <issuer> --valid-for <seconds>
26+
```
27+
28+
#### Options
29+
- `--cache-file <file>`: Path to the keycache SQLite database file
30+
- `--jwks <file>`: Path to the JWKS file to store
31+
- `--issuer <issuer>`: Issuer URL for the JWKS
32+
- `--valid-for <seconds>`: How long the key should be valid (in seconds)
33+
- `--help`: Show help message
34+
35+
#### Example
36+
```bash
37+
scitokens-keycache --cache-file /opt/scitokens/offline.db \
38+
--jwks issuer_keys.json \
39+
--issuer https://tokens.example.com \
40+
--valid-for 86400
41+
```
42+
43+
### 3. New API Function
44+
45+
A new C API function allows programmatic storage of JWKS with explicit expiration times:
46+
47+
```c
48+
int keycache_set_jwks_with_expiry(const char *issuer, const char *jwks,
49+
int64_t expires_at, char **err_msg);
50+
```
51+
52+
Where `expires_at` is the expiration time as a Unix timestamp (seconds since epoch).
53+
54+
## Usage Workflow
55+
56+
### Setting up an Offline Cache
57+
58+
1. **Create JWKS file**: Save the issuer's public keys in a JSON Web Key Set format
59+
```json
60+
{
61+
"keys": [
62+
{
63+
"kty": "EC",
64+
"kid": "key-1",
65+
"use": "sig",
66+
"alg": "ES256",
67+
"x": "...",
68+
"y": "...",
69+
"crv": "P-256"
70+
}
71+
]
72+
}
73+
```
74+
75+
2. **Create cache file**: Use the scitokens-keycache tool
76+
```bash
77+
scitokens-keycache --cache-file /opt/tokens/cache.db \
78+
--jwks issuer_keys.json \
79+
--issuer https://tokens.example.com \
80+
--valid-for 2592000 # 30 days
81+
```
82+
83+
3. **Configure application**: Set the environment variable
84+
```bash
85+
export SCITOKENS_KEYCACHE_FILE=/opt/tokens/cache.db
86+
```
87+
88+
### Using the Offline Cache
89+
90+
Once configured, the existing scitokens API functions work normally:
91+
92+
```c
93+
char *jwks = NULL;
94+
char *err_msg = NULL;
95+
int result = keycache_get_cached_jwks("https://tokens.example.com", &jwks, &err_msg);
96+
if (result == 0 && jwks) {
97+
// Process the JWKS
98+
free(jwks);
99+
}
100+
```
101+
102+
## Backward Compatibility
103+
104+
All existing functionality remains unchanged. The new features are:
105+
- Additive API extensions
106+
- Optional environment variable
107+
- New command-line tool
108+
109+
Existing code will continue to work without modification.
110+
111+
## Cache Location Priority
112+
113+
The cache file location is determined in this order:
114+
1. `SCITOKENS_KEYCACHE_FILE` environment variable (highest priority - for offline use)
115+
2. User-configured cache home via config API
116+
3. `XDG_CACHE_HOME` environment variable
117+
4. `~/.cache` directory (lowest priority)
118+
119+
## Security Considerations
120+
121+
- Ensure offline cache files have appropriate file permissions (600 or 640)
122+
- Regularly update offline caches with fresh keys before expiration
123+
- Consider key rotation policies when setting expiration times
124+
- Validate JWKS content before adding to offline caches

src/scitokens.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include <sys/select.h>
88
#include <time.h>
9+
#include <stdint.h>
910

1011
#ifdef __cplusplus
1112
#include <ctime>

test/main.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,80 @@ TEST_F(KeycacheTest, RefreshExpiredTest) {
841841
EXPECT_EQ(jwks_str, "{\"keys\": []}");
842842
}
843843

844+
TEST_F(KeycacheTest, OfflineCacheFileTest) {
845+
char *err_msg = nullptr;
846+
847+
// Create a temporary file for offline cache
848+
char temp_file[] = "/tmp/test_offline_cache_XXXXXX";
849+
int fd = mkstemp(temp_file);
850+
ASSERT_TRUE(fd != -1);
851+
close(fd);
852+
unlink(temp_file); // Remove the file so SQLite can create it
853+
854+
// Set the environment variable
855+
setenv("SCITOKENS_KEYCACHE_FILE", temp_file, 1);
856+
857+
// Store JWKS with explicit expiry time (1 hour from now)
858+
time_t now = time(nullptr);
859+
int64_t expires_at = static_cast<int64_t>(now) + 3600;
860+
861+
auto rv = keycache_set_jwks_with_expiry("https://offline.test.com",
862+
demo_scitokens2.c_str(),
863+
expires_at, &err_msg);
864+
ASSERT_TRUE(rv == 0) << err_msg;
865+
866+
// Retrieve JWKS from offline cache
867+
char *jwks;
868+
rv = keycache_get_cached_jwks("https://offline.test.com", &jwks, &err_msg);
869+
ASSERT_TRUE(rv == 0) << err_msg;
870+
ASSERT_TRUE(jwks != nullptr);
871+
std::string jwks_str(jwks);
872+
free(jwks);
873+
874+
EXPECT_EQ(demo_scitokens2, jwks_str);
875+
876+
// Clean up
877+
unlink(temp_file);
878+
unsetenv("SCITOKENS_KEYCACHE_FILE");
879+
}
880+
881+
TEST_F(KeycacheTest, OfflineCacheExpiryTest) {
882+
char *err_msg = nullptr;
883+
884+
// Create a temporary file for offline cache
885+
char temp_file[] = "/tmp/test_offline_expiry_XXXXXX";
886+
int fd = mkstemp(temp_file);
887+
ASSERT_TRUE(fd != -1);
888+
close(fd);
889+
unlink(temp_file); // Remove the file so SQLite can create it
890+
891+
// Set the environment variable
892+
setenv("SCITOKENS_KEYCACHE_FILE", temp_file, 1);
893+
894+
// Store JWKS with expiry time in the past (already expired)
895+
time_t now = time(nullptr);
896+
int64_t expires_at = static_cast<int64_t>(now) - 3600; // 1 hour ago
897+
898+
auto rv = keycache_set_jwks_with_expiry("https://expired.test.com",
899+
demo_scitokens2.c_str(),
900+
expires_at, &err_msg);
901+
ASSERT_TRUE(rv == 0) << err_msg;
902+
903+
// Retrieve JWKS from offline cache - should get empty keys due to expiry
904+
char *jwks;
905+
rv = keycache_get_cached_jwks("https://expired.test.com", &jwks, &err_msg);
906+
ASSERT_TRUE(rv == 0) << err_msg;
907+
ASSERT_TRUE(jwks != nullptr);
908+
std::string jwks_str(jwks);
909+
free(jwks);
910+
911+
EXPECT_EQ(jwks_str, "{\"keys\": []}");
912+
913+
// Clean up
914+
unlink(temp_file);
915+
unsetenv("SCITOKENS_KEYCACHE_FILE");
916+
}
917+
844918
class IssuerSecurityTest : public ::testing::Test {
845919
protected:
846920
void SetUp() override {

0 commit comments

Comments
 (0)