Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e05a13d
Initial plan
Copilot Dec 9, 2025
987c5dc
Add full integration test with JWKS server and TLS support
Copilot Dec 9, 2025
d322861
Fix code review issues: Add missing headers and clarify variable usage
Copilot Dec 9, 2025
6816080
Fix security issues in test server
Copilot Dec 9, 2025
f9cc780
Update .gitignore and remove CodeQL build artifacts
Copilot Dec 9, 2025
bb2d9e4
Initial plan
Copilot Dec 9, 2025
29c4b4b
Rebase on master and use scitokens-generate-jwks
Copilot Dec 9, 2025
f8c0251
Fix race condition in JWKS server port binding
Copilot Dec 9, 2025
6fa0eae
Add Python cache files to .gitignore
Copilot Dec 9, 2025
6d41abd
Fix devcontainer build: handle existing GID/UID gracefully
bbockelm Dec 9, 2025
77b58d9
Fix vscode user creation by removing UID/GID constraints
bbockelm Dec 9, 2025
f093dba
Fix EC coordinate extraction for OpenSSL 3.0.2+
bbockelm Dec 9, 2025
0fc850a
Optimize integration test setup and teardown
bbockelm Dec 9, 2025
1220f0d
Add git safe.directory config to devcontainer postCreateCommand
bbockelm Dec 9, 2025
fff7a9f
Improve TLS compatibility for OpenSSL 3.0.2 in Ubuntu 22.04
bbockelm Dec 9, 2025
891ffdd
Add server log output to teardown and improve TLS compatibility
bbockelm Dec 9, 2025
ab10758
Apply clang-format to generate_jwks.cpp
bbockelm Dec 9, 2025
079fe43
Fix TLS session resumption for OpenSSL 3.0.2 on Ubuntu 22.04
bbockelm Dec 9, 2025
a8261c6
Add Content-Length headers for HTTP/1.1 keep-alive
bbockelm Dec 9, 2025
7454a72
Add GitHub Copilot instructions for project
bbockelm Dec 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,10 @@ RUN apt-get update \

# Create a non-root user to use
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID

RUN groupadd --gid $USER_GID $USERNAME \
&& useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \
# Create user and group without forcing specific UID/GID
RUN groupadd $USERNAME \
&& useradd -g $USERNAME -m $USERNAME \
&& echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
&& chmod 0440 /etc/sudoers.d/$USERNAME

Expand Down
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
// "forwardPorts": [],

// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "git submodule update --init --recursive",
"postCreateCommand": "git config --global --add safe.directory /workspaces/scitokens-cpp && git submodule update --init --recursive",

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
"remoteUser": "vscode",
Expand Down
146 changes: 146 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# GitHub Copilot Instructions for scitokens-cpp

## Project Overview

scitokens-cpp is a C++ library for creating and validating SciTokens (JWT-based authorization tokens for scientific computing). The library uses JWT-cpp for token operations and supports OIDC discovery with JWKS for public key distribution.

## Building the Project

### Prerequisites

- CMake 3.10 or later
- C++11 compatible compiler (gcc, clang)
- OpenSSL 1.1.1 or later (3.0+ recommended)
- libuuid
- sqlite3
- jwt-cpp (included as vendor submodule)

### Build Commands

```bash
# Create build directory
mkdir -p build
cd build

# Configure with CMake (enable tests with -DSCITOKENS_BUILD_UNITTESTS=ON)
cmake .. -DSCITOKENS_BUILD_UNITTESTS=ON

# Build all targets
make

# Install (optional)
sudo make install
```

### CMake Build Options

- Tests are **disabled by default** - use `-DSCITOKENS_BUILD_UNITTESTS=ON` to enable
- Build produces:
- `libSciTokens.so` - Main library
- `scitokens-test` - Unit tests (Google Test)
- `scitokens-integration-test` - Integration tests with real HTTPS server
- `scitokens-generate-jwks` - JWKS generation utility
- Command-line tools: `scitokens-verify`, `scitokens-create`, `scitokens-list-access`, `scitokens-test-access`

## Running Tests

### Unit Tests

```bash
cd build
./scitokens-test
```

Expected: 29 unit tests should pass

### Integration Tests

Integration tests use CTest fixtures with setup/teardown phases:

```bash
cd build/test
ctest --output-on-failure
```

Or run specific test phases:
```bash
ctest -R integration::setup # Start HTTPS JWKS server
ctest -R integration::test # Run integration tests
ctest -R integration::teardown # Stop server
```

**Integration test infrastructure:**
- `test/jwks_server.py` - Python HTTPS server with OIDC discovery and JWKS endpoints
- `test/integration-test-setup.sh` - Generates TLS certificates and starts server
- `test/integration-test-teardown.sh` - Stops server gracefully
- `test/integration_test.cpp` - C++ tests using real HTTPS connections

Expected: 3 integration tests should pass (total time ~1-2 seconds)

### All Tests

```bash
cd build/test
ctest --output-on-failure
```

Expected: 32 total tests (29 unit + 3 integration)

## Code Style

- C++11 standard
- Use `clang-format` for formatting (configuration in project root)
- Format before committing: `clang-format -i src/*.cpp src/*.h`

## Testing Infrastructure Details

### JWKS Server (test/jwks_server.py)

Python HTTPS server that provides:
- `/.well-known/openid-configuration` - OIDC discovery document
- `/oauth2/certs` - JWKS public key endpoint

Server features:
- HTTP/1.1 with keep-alive support
- TLS 1.2+ with self-signed certificates
- Graceful shutdown with SIGTERM
- Logs to `build/tests/integration/server.log`

### Integration Test Flow

1. **Setup**: Generate EC P-256 key pair, create JWKS, generate TLS certificates, start HTTPS server
2. **Test**: Create tokens, verify with JWKS discovery, test dynamic issuer enforcement
3. **Teardown**: Stop server, print logs if tests failed

### Debugging Integration Tests

If integration tests fail:
1. Check server log: `cat build/tests/integration/server.log`
2. Verify server started: `cat build/tests/integration/server_ready`
3. Test HTTPS manually: `curl -k https://localhost:<port>/.well-known/openid-configuration`

## Key Files

- `src/scitokens.cpp`, `src/scitokens.h` - Main library API
- `src/generate_jwks.cpp` - JWKS generation (EC P-256 keys)
- `src/scitokens_internal.cpp` - Token validation and OIDC discovery
- `src/scitokens_cache.cpp` - JWKS caching
- `test/integration_test.cpp` - End-to-end integration tests
- `test/main.cpp` - Google Test unit tests

## Development Workflow

1. Make code changes
2. Build: `cd build && cmake .. && make`
3. Run unit tests: `./scitokens-test`
4. Run integration tests: `cd test && ctest --output-on-failure`
5. Format code: `clang-format -i <modified-files>`
6. Commit with descriptive message

## CI/CD

GitHub Actions runs tests on:
- Ubuntu 22.04 (OpenSSL 3.0.2)
- Ubuntu 24.04 (OpenSSL 3.0.13)

Integration tests verify TLS compatibility across OpenSSL versions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ build
_codeql_build_dir
_codeql_detected_source_root

*.pyc
__pycache__/
52 changes: 33 additions & 19 deletions src/generate_jwks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,29 +128,33 @@ std::string base64url_encode(const unsigned char *data, size_t len) {
bool extract_ec_coordinates(EVP_PKEY *pkey, std::string &x_coord,
std::string &y_coord) {
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
size_t pub_key_len = 0;

if (EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, nullptr,
0, &pub_key_len) != 1) {
// For OpenSSL 3.0+, use the BIGNUM parameter API which is more reliable
BIGNUM *x_bn = nullptr;
BIGNUM *y_bn = nullptr;

if (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &x_bn) != 1 ||
EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &y_bn) != 1) {
BN_free(x_bn);
BN_free(y_bn);
return false;
}

std::unique_ptr<unsigned char[]> pub_key_buf(
new unsigned char[pub_key_len]);
std::unique_ptr<BIGNUM, decltype(&BN_free)> x(x_bn, BN_free);
std::unique_ptr<BIGNUM, decltype(&BN_free)> y(y_bn, BN_free);

if (EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY,
pub_key_buf.get(), pub_key_len,
&pub_key_len) != 1) {
return false;
}
// Convert BIGNUMs to fixed-size byte arrays (32 bytes for P-256)
unsigned char x_buf[32] = {0};
unsigned char y_buf[32] = {0};

// For uncompressed EC point format: 0x04 || X || Y
if (pub_key_len != 65 || pub_key_buf[0] != 0x04) {
return false;
}
int x_len = BN_num_bytes(x.get());
int y_len = BN_num_bytes(y.get());

x_coord = base64url_encode(pub_key_buf.get() + 1, 32);
y_coord = base64url_encode(pub_key_buf.get() + 33, 32);
// Pad with zeros on the left if necessary
BN_bn2bin(x.get(), x_buf + (32 - x_len));
BN_bn2bin(y.get(), y_buf + (32 - y_len));

x_coord = base64url_encode(x_buf, 32);
y_coord = base64url_encode(y_buf, 32);
#else
std::unique_ptr<EC_KEY, decltype(&EC_KEY_free)> ec_key(
EVP_PKEY_get1_EC_KEY(pkey), EC_KEY_free);
Expand All @@ -167,8 +171,18 @@ bool extract_ec_coordinates(EVP_PKEY *pkey, std::string &x_coord,
std::unique_ptr<BIGNUM, decltype(&BN_free)> x(BN_new(), BN_free);
std::unique_ptr<BIGNUM, decltype(&BN_free)> y(BN_new(), BN_free);

if (!EC_POINT_get_affine_coordinates_GFp(group, pub_key, x.get(), y.get(),
nullptr)) {
// Use EC_POINT_get_affine_coordinates for OpenSSL 1.1.1+
// or EC_POINT_get_affine_coordinates_GFp for older versions
int result = 0;
#if OPENSSL_VERSION_NUMBER >= 0x10101000L
result = EC_POINT_get_affine_coordinates(group, pub_key, x.get(), y.get(),
nullptr);
#else
result = EC_POINT_get_affine_coordinates_GFp(group, pub_key, x.get(),
y.get(), nullptr);
#endif

if (result != 1) {
return false;
}

Expand Down
49 changes: 49 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,52 @@ add_test(
COMMAND
${CMAKE_CURRENT_BINARY_DIR}/scitokens-gtest
)

# Integration test executable
add_executable(scitokens-integration-test integration_test.cpp)
if( NOT SCITOKENS_EXTERNAL_GTEST )
add_dependencies(scitokens-integration-test gtest)
endif()
target_link_libraries(scitokens-integration-test SciTokens "${LIBGTEST}" pthread)

# Integration test fixture - setup
add_test(
NAME
integration::setup
COMMAND
${CMAKE_CURRENT_SOURCE_DIR}/integration-test-setup.sh integration
)

set_tests_properties(integration::setup
PROPERTIES
FIXTURES_SETUP integration
ENVIRONMENT "BINARY_DIR=${CMAKE_BINARY_DIR};SOURCE_DIR=${PROJECT_SOURCE_DIR}"
)

# Integration test fixture - teardown
add_test(
NAME
integration::teardown
COMMAND
${CMAKE_CURRENT_SOURCE_DIR}/integration-test-teardown.sh integration
)

set_tests_properties(integration::teardown
PROPERTIES
FIXTURES_CLEANUP integration
ENVIRONMENT "BINARY_DIR=${CMAKE_BINARY_DIR}"
)

# Integration test
add_test(
NAME
integration::test
COMMAND
${CMAKE_CURRENT_BINARY_DIR}/scitokens-integration-test
)

set_tests_properties(integration::test
PROPERTIES
FIXTURES_REQUIRED integration
ENVIRONMENT "BINARY_DIR=${CMAKE_BINARY_DIR};CURL_CA_BUNDLE=${CMAKE_BINARY_DIR}/tests/integration/current/ca-cert.pem"
)
Loading
Loading