Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore!(sdk): remove v1 support #393

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 0 additions & 9 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -231,15 +231,6 @@ jobs:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: echo "- [Client Library](https://github.com/opentdf/web-sdk/pkgs/npm/client)">>$GITHUB_STEP_SUMMARY
- run: echo "- [Command Line Tool](https://github.com/opentdf/web-sdk/pkgs/npm/cli)">>$GITHUB_STEP_SUMMARY
- name: trigger xtest
run: >-
curl -XPOST -u "virtru-cloudnative:${{secrets.PERSONAL_ACCESS_TOKEN}}"
-H "Accept: application/vnd.github.everest-preview+json"
-H "Content-Type: application/json"
"https://api.github.com/repos/opentdf/backend/dispatches"
--data '{"event_type":"xtest","client_payload":{"version":"'${FULL_VERSION%%+*}'"}}'
env:
FULL_VERSION: ${{ steps.guess-build-metadata.outputs.FULL_VERSION }}
- name: Publish documentation to gh-pages
uses: JamesIves/github-pages-deploy-action@v4.6.0
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/roundtrip/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "web-sdk-roundtrip",
"version": "0.0.1",
"description": "Simple example to encrypt and decrypt files with quickstart backend.",
"description": "Simple example to encrypt and decrypt files.",
"scripts": {},
"dependencies": {
"@opentdf/ctl": "file:../../../cli/opentdf-ctl-0.1.0.tgz"
Expand Down
10 changes: 3 additions & 7 deletions .github/workflows/roundtrip/wait-and-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,9 @@ _configure_app() {
return 0
}

if [ $1 = backend ]; then
VITE_PROXY='{"/api":{"target":"http://localhost:5432","xfwd":true},"/auth":{"target":"http://localhost:5432","xfwd":true}}'
VITE_TDF_CFG='{"oidc":{"host":"http://localhost:65432/auth/realms/tdf","clientId":"browsertest"},"kas":"http://localhost:65432/api/kas","reader":"https://secure.virtru.com/start?htmlProtocol=1"}'
else # if [ $1 = platform ]; then
VITE_PROXY='{"/kas":{"target":"http://localhost:8080","xfwd":true},"/auth":{"target":"http://localhost:8888","xfwd":true}}'
VITE_TDF_CFG='{"oidc":{"host":"http://localhost:65432/auth/realms/opentdf","clientId":"browsertest"},"kas":"http://localhost:65432/kas","reader":"https://secure.virtru.com/start?htmlProtocol=1"}'
fi
VITE_PROXY='{"/kas":{"target":"http://localhost:8080","xfwd":true},"/auth":{"target":"http://localhost:8888","xfwd":true}}'
VITE_TDF_CFG='{"oidc":{"host":"http://localhost:65432/auth/realms/opentdf","clientId":"browsertest"},"kas":"http://localhost:65432/kas","reader":"https://secure.virtru.com/start?htmlProtocol=1"}'

export VITE_PROXY
export VITE_TDF_CFG

Expand Down
53 changes: 8 additions & 45 deletions lib/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@
"watch": "(trap 'kill 0' SIGINT; npm run build && (npm run build:watch & npm run test -- --watch))"
},
"dependencies": {
"axios": "^1.6.1",
"axios-retry": "^3.9.0",
"base64-js": "^1.5.1",
"browser-fs-access": "^0.34.1",
"buffer-crc32": "^0.2.13",
Expand Down
116 changes: 63 additions & 53 deletions lib/src/access.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { type AuthProvider } from './auth/auth.js';
import {
ConfigurationError,
InvalidFileError,
NetworkError,
PermissionDeniedError,
Expand All @@ -8,14 +9,16 @@ import {
} from './errors.js';
import { pemToCryptoPublicKey, validateSecureUrl } from './utils.js';

export class RewrapRequest {
signedRequestToken = '';
}
export type RewrapRequest = {
signedRequestToken: string;
};

export class RewrapResponse {
entityWrappedKey = '';
sessionPublicKey = '';
}
export type RewrapResponse = {
metadata: Record<string, unknown>;
entityWrappedKey: string;
sessionPublicKey: string;
schemaVersion: string;
};

/**
* Get a rewrapped access key to the document, if possible
Expand All @@ -40,8 +43,10 @@ export async function fetchWrappedKey(
body: JSON.stringify(requestBody),
});

let response: Response;

try {
const response = await fetch(req.url, {
response = await fetch(req.url, {
method: req.method,
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
Expand All @@ -51,28 +56,33 @@ export async function fetchWrappedKey(
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
body: req.body as BodyInit,
});
} catch (e) {
throw new NetworkError(`unable to fetch wrapped key from [${url}]`, e);
}

if (!response.ok) {
switch (response.status) {
case 400:
throw new InvalidFileError(
`400 for [${req.url}]: rewrap failure [${await response.text()}]`
);
case 401:
throw new UnauthenticatedError(`401 for [${req.url}]`);
case 403:
throw new PermissionDeniedError(`403 for [${req.url}]`);
default:
throw new NetworkError(
`${req.method} ${req.url} => ${response.status} ${response.statusText}`
if (!response.ok) {
switch (response.status) {
case 400:
throw new InvalidFileError(
`400 for [${req.url}]: rewrap bad request [${await response.text()}]`
);
case 401:
throw new UnauthenticatedError(`401 for [${req.url}]; rewrap auth failure`);
case 403:
throw new PermissionDeniedError(`403 for [${req.url}]; rewrap permission denied`);
default:
if (response.status >= 500) {
throw new ServiceError(
`${response.status} for [${req.url}]: rewrap failure due to service error [${await response.text()}]`
);
}
}
throw new NetworkError(
`${req.method} ${req.url} => ${response.status} ${response.statusText}`
);
}

return response.json();
} catch (e) {
throw new NetworkError(`unable to fetch wrapped key from [${url}]: ${e}`);
}

return response.json();
}

export type KasPublicKeyAlgorithm = 'ec:secp256r1' | 'rsa:2048';
Expand Down Expand Up @@ -100,7 +110,7 @@ export type KasPublicKeyInfo = {
key: Promise<CryptoKey>;
};

async function noteInvalidPublicKey(url: string, r: Promise<CryptoKey>): Promise<CryptoKey> {
async function noteInvalidPublicKey(url: URL, r: Promise<CryptoKey>): Promise<CryptoKey> {
try {
return await r;
} catch (e) {
Expand All @@ -116,14 +126,36 @@ async function noteInvalidPublicKey(url: string, r: Promise<CryptoKey>): Promise
* the value from `${kas}/kas_public_key`.
*/
export async function fetchECKasPubKey(kasEndpoint: string): Promise<KasPublicKeyInfo> {
return fetchKasPubKey(kasEndpoint, 'ec:secp256r1');
}

export async function fetchKasPubKey(
kasEndpoint: string,
algorithm?: KasPublicKeyAlgorithm
): Promise<KasPublicKeyInfo> {
if (!kasEndpoint) {
throw new ConfigurationError('KAS definition not found');
}
// Logs insecure KAS. Secure is enforced in constructor
validateSecureUrl(kasEndpoint);
const pkUrlV2 = `${kasEndpoint}/v2/kas_public_key?algorithm=ec:secp256r1&v=2`;
const kasPubKeyResponseV2 = await fetch(pkUrlV2);
const infoStatic = { url: kasEndpoint, algorithm: algorithm || 'rsa:2048' };

const pkUrlV2 = new URL('/v2/kas_public_key?v=2', kasEndpoint);
if (!pkUrlV2) {
throw new ConfigurationError(`KAS definition invalid: [${kasEndpoint}]`);
}
pkUrlV2.searchParams.set('algorithm', infoStatic.algorithm);

let kasPubKeyResponseV2: Response;
try {
kasPubKeyResponseV2 = await fetch(pkUrlV2);
} catch (e) {
throw new NetworkError(`unable to fetch public key from [${pkUrlV2}]`, e);
}
if (!kasPubKeyResponseV2.ok) {
switch (kasPubKeyResponseV2.status) {
case 404:
// v2 not implemented, perhaps a legacy server
break;
throw new ConfigurationError(`404 for [${pkUrlV2}]`);
case 401:
throw new UnauthenticatedError(`401 for [${pkUrlV2}]`);
case 403:
Expand All @@ -133,28 +165,6 @@ export async function fetchECKasPubKey(kasEndpoint: string): Promise<KasPublicKe
`${pkUrlV2} => ${kasPubKeyResponseV2.status} ${kasPubKeyResponseV2.statusText}`
);
}
// most likely a server that does not implement v2 endpoint, so no key identifier
const pkUrlV1 = `${kasEndpoint}/kas_public_key?algorithm=ec:secp256r1`;
const r2 = await fetch(pkUrlV1);
if (!r2.ok) {
switch (r2.status) {
case 401:
throw new UnauthenticatedError(`401 for [${pkUrlV2}]`);
case 403:
throw new PermissionDeniedError(`403 for [${pkUrlV2}]`);
default:
throw new NetworkError(
`unable to load KAS public key from [${pkUrlV1}]. Received [${r2.status}:${r2.statusText}]`
);
}
}
const pem = await r2.json();
return {
key: noteInvalidPublicKey(pkUrlV1, pemToCryptoPublicKey(pem)),
publicKey: pem,
url: kasEndpoint,
algorithm: 'ec:secp256r1',
};
}
const jsonContent = await kasPubKeyResponseV2.json();
const { publicKey, kid }: KasPublicKeyInfo = jsonContent;
Expand Down
Loading
Loading