Files
daily-notification-plugin/test-apps/daily-notification-test/INVESTIGATION_JWT_ALGORITHM_RESULTS.md
2025-10-31 09:56:23 +00:00

221 lines
6.5 KiB
Markdown

# JWT Algorithm Investigation Results
**Date**: 2025-10-31
**Investigator**: Auto (AI Assistant)
**Status**: ✅ **INVESTIGATION COMPLETE**
---
## 🔴 CRITICAL FINDING: DID-Based JWT Signing Required (ES256K)
### Conclusion
**The endorser-ch API expects DID-based JWTs signed with ES256K (or ES256K-R), NOT HMAC-SHA256 (HS256).**
The current implementation in `TestNativeFetcher.java` using HMAC-SHA256 is **INCORRECT** and will fail authentication.
---
## Investigation Details
### TimeSafari Repository Findings
**Location**: `~/projects/timesafari/crowd-funder-for-time-pwa/src/libs/crypto/vc/index.ts`
**Function**: `createEndorserJwtForKey()`
**Implementation**:
```typescript
export async function createEndorserJwtForKey(
account: KeyMetaWithPrivate,
payload: object,
expiresIn?: number,
) {
if (account?.identity) {
const identity: IIdentifier = JSON.parse(account.identity!);
const privateKeyHex = identity.keys[0].privateKeyHex;
const signer = await SimpleSigner(privateKeyHex as string);
const options = {
// alg: "ES256K", // "K" is the default, "K-R" is used by the server in tests
issuer: account.did,
signer: signer,
expiresIn: undefined as number | undefined,
};
if (expiresIn) {
options.expiresIn = expiresIn;
}
return didJwt.createJWT(payload, options);
}
// ... passkey handling ...
}
```
**Key Points**:
- Uses `did-jwt.createJWT()` library
- Uses `SimpleSigner(privateKeyHex)` - signs with Ethereum private key
- Algorithm is **ES256K** (default)
- Signs with DID private key, NOT a shared secret
**Library Used**: `did-jwt` (DID-based JWT library)
---
### endorser-ch Repository Findings
**Location**: `~/projects/timesafari/endorser-ch/src/api/services/vc/index.js`
**Function**: `decodeAndVerifyJwt()`
**Implementation**:
```javascript
export async function decodeAndVerifyJwt(jwt) {
const pieces = jwt.split('.')
const header = JSON.parse(base64url.decode(pieces[0]))
const payload = JSON.parse(base64url.decode(pieces[1]))
const issuerDid = payload.iss
if (issuerDid.startsWith(ETHR_DID_PREFIX)) {
try {
const verifiedResult = await didJwt.verifyJWT(jwt, {resolver})
return verifiedResult
} catch (e) {
return Promise.reject({
clientError: {
message: `JWT failed verification: ` + e.toString(),
code: JWT_VERIFY_FAILED_CODE
}
})
}
}
// ... other DID methods ...
}
```
**Key Points**:
- Uses `did-jwt.verifyJWT(jwt, {resolver})` - DID-based verification
- Verifies signature using DID resolver (resolves DID to public key)
- **NO shared secret used** - uses DID public key from resolver
- Algorithm: **ES256K** (implicit from did-jwt library)
**Middleware Location**: `~/projects/timesafari/endorser-ch/src/common/server.js`
**Authentication Flow**:
```javascript
decodeAndVerifyJwt(authorizationJwt)
.then((result) => {
const { header, issuer, payload, verified } = result
if (!verified) {
res.status(400).json({
error: {
message: "Signature failed validation.",
code: ERROR_CODES.JWT_VERIFY_FAILED
}
}).end()
} else {
res.locals.authTokenIssuer = issuer
next()
}
})
```
---
## Impact on Current Implementation
### Current Implementation (WRONG)
**File**: `test-apps/daily-notification-test/android/app/src/main/java/com/timesafari/dailynotification/test/TestNativeFetcher.java`
**Current Approach**: HMAC-SHA256 with shared secret
```java
Mac hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(
jwtSecret.getBytes(StandardCharsets.UTF_8),
"HmacSHA256"
);
hmac.init(secretKey);
byte[] signatureBytes = hmac.doFinal(unsignedToken.getBytes(StandardCharsets.UTF_8));
```
**Problem**: Server expects ES256K with DID private key, NOT HMAC-SHA256 with shared secret
---
## Required Changes
### Action Required
1. **Remove HMAC-SHA256 implementation** - This is completely wrong
2. **Implement DID-based signing (ES256K)** - Sign with Ethereum private key
3. **Access DID private key** - Need to retrieve private key from DID/account storage
4. **Use did-jwt-java or web3j** - Java library for DID-based JWT signing
### Implementation Options
#### Option 1: Use did-jwt-java Library (Recommended)
```java
// Add dependency to build.gradle
implementation 'io.uport:uport-did-jwt:3.1.0'
// Sign with DID private key
import io.uport.sdk.did.jwt.DIDJWT;
import io.uport.sdk.did.jwt.SimpleSigner;
String privateKeyHex = getPrivateKeyForDid(activeDid); // Need to implement
SimpleSigner signer = new SimpleSigner(privateKeyHex);
String jwt = DIDJWT.createJWT(payload, signer, issuer: activeDid);
```
#### Option 2: Use web3j for Ethereum Signing
```java
// Add dependency
implementation 'org.web3j:core:4.9.8'
import org.web3j.crypto.ECKeyPair;
import org.web3j.crypto.Sign;
ECKeyPair keyPair = getKeyPairForDid(activeDid); // Need to implement
Sign.SignatureData signature = Sign.signMessage(
unsignedToken.getBytes(StandardCharsets.UTF_8),
keyPair
);
// Then encode signature according to ES256K format
```
---
## Next Steps
1. **Remove `jwtSecret` parameter** - No longer needed (shared secret not used)
2. **Add DID private key retrieval** - Need mechanism to get private key for `activeDid`
3. **Implement ES256K signing** - Using did-jwt-java or web3j
4. **Update `configureNativeFetcher()`** - Remove `jwtSecret`, add private key retrieval mechanism
5. **Test with real API** - Verify JWTs are accepted by endorser-ch server
---
## References
- **TimeSafari Implementation**: `~/projects/timesafari/crowd-funder-for-time-pwa/src/libs/crypto/vc/index.ts`
- **endorser-ch Verification**: `~/projects/timesafari/endorser-ch/src/api/services/vc/index.js`
- **did-jwt Library**: https://github.com/decentralized-identity/did-jwt
- **did-jwt-java**: https://github.com/uport-project/uport-did-jwt (if available) or alternative Java DID libraries
---
## Evidence Summary
| Component | Finding | Evidence |
|-----------|---------|----------|
| **TimeSafari JWT Creation** | ✅ DID-based (ES256K) | Uses `didJwt.createJWT()` with `SimpleSigner(privateKeyHex)` |
| **endorser-ch JWT Verification** | ✅ DID-based (ES256K) | Uses `didJwt.verifyJWT(jwt, {resolver})` |
| **Current TestNativeFetcher** | ❌ HMAC-SHA256 | Uses `Mac.getInstance("HmacSHA256")` with shared secret |
| **Shared Secret Config** | ❌ Not Used | No `JWT_SECRET` found in endorser-ch, no shared secret in TimeSafari |
---
**Status**: Investigation complete. Implementation changes required.