-
Notifications
You must be signed in to change notification settings - Fork 340
pgcrypto #856
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
Merged
Merged
pgcrypto #856
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
3937098
try again pgcrypto on CI
tdrz 8f53e17
add openpgp
tdrz fc162f2
style
tdrz a99c273
update submodule
tdrz 35e2bc0
udpate submodule
tdrz a7d7ce0
print error messages on pglite socket test
tdrz 7cb160f
CI: do not fail fast
tdrz 43c5085
test using node v22.20.0
tdrz de9dcca
test using node v22.20.0
tdrz c4866b4
update submodule; disable pgcrypto test
tdrz 11d05fe
readd pgcrypto test
tdrz 4fdd284
update submodule
tdrz 98340f2
try to identify what function call is missing
tdrz d814208
update submodule
tdrz f648ebc
update submodule
tdrz c3d349a
update submodule
tdrz 088c7ea
trying to find missing symbol
tdrz 45d7005
update submodule
tdrz 986e954
update submodule
tdrz 21804b9
update submodule
tdrz d46771d
remove removeFunction calls on pglite end
tdrz b55618e
undo changes to CI workflow
tdrz 380b815
update submodule
tdrz 3a00287
undo pglite.ts changes
tdrz 3458462
more pgcrypto tests
tdrz e0fe161
update size
tdrz c7e6d88
remove wasmTable
tdrz d865dcb
update submodule
tdrz 5de09e3
style
tdrz 8775a24
update submodule
tdrz 2f062db
merge main
tdrz 8959a02
udpate submodule
tdrz 0c683bc
update submodule
80115d9
changeset
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| '@electric-sql/pglite-socket': patch | ||
| '@electric-sql/pglite': patch | ||
| --- | ||
|
|
||
| added pgcrypto extension |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import type { | ||
| Extension, | ||
| ExtensionSetupResult, | ||
| PGliteInterface, | ||
| } from '../interface' | ||
|
|
||
| const setup = async (_pg: PGliteInterface, _emscriptenOpts: any) => { | ||
| return { | ||
| bundlePath: new URL('../../release/pgcrypto.tar.gz', import.meta.url), | ||
| } satisfies ExtensionSetupResult | ||
| } | ||
|
|
||
| export const pgcrypto = { | ||
| name: 'pgcrypto', | ||
| setup, | ||
| } satisfies Extension |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,252 @@ | ||
| import { describe, it, expect } from 'vitest' | ||
| import { PGlite } from '../../dist/index.js' | ||
| import { pgcrypto } from '../../dist/contrib/pgcrypto.js' | ||
| import * as openpgp from 'openpgp' | ||
|
|
||
| describe('pg_pgcryptotrgm', () => { | ||
| it('digest', async () => { | ||
| const pg = new PGlite({ | ||
| extensions: { | ||
| pgcrypto, | ||
| }, | ||
| }) | ||
|
|
||
| await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') | ||
|
|
||
| const res = await pg.query( | ||
| "SELECT encode(digest(convert_to('test', 'UTF8'), 'sha1'), 'hex') as value;", | ||
| ) | ||
| expect(res.rows[0].value, 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3') | ||
| }) | ||
|
|
||
| it('hmac', async () => { | ||
| const pg = new PGlite({ | ||
| extensions: { | ||
| pgcrypto, | ||
| }, | ||
| }) | ||
|
|
||
| await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') | ||
|
|
||
| const res = await pg.query( | ||
| "SELECT encode(hmac(convert_to('test', 'UTF8'), convert_to('key', 'UTF8'), 'sha1'), 'hex') as value;", | ||
| ) | ||
| expect(res.rows[0].value).toEqual( | ||
| '671f54ce0c540f78ffe1e26dcf9c2a047aea4fda', | ||
| ) | ||
| }) | ||
|
|
||
| it('crypt', async () => { | ||
| const pg = new PGlite({ | ||
| extensions: { | ||
| pgcrypto, | ||
| }, | ||
| }) | ||
|
|
||
| await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') | ||
|
|
||
| const res = await pg.query("SELECT crypt('test', gen_salt('bf')) as value;") | ||
| expect(res.rows[0].value.length).toEqual(60) | ||
| }) | ||
|
|
||
| it('gen_salt', async () => { | ||
| const pg = new PGlite({ | ||
| extensions: { | ||
| pgcrypto, | ||
| }, | ||
| }) | ||
|
|
||
| await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') | ||
|
|
||
| const res = await pg.query("SELECT gen_salt('bf') as value;") | ||
| expect(res.rows[0].value.length).toEqual(29) | ||
| }) | ||
|
|
||
| it('armor', async () => { | ||
| const pg = new PGlite({ | ||
| extensions: { | ||
| pgcrypto, | ||
| }, | ||
| }) | ||
|
|
||
| await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') | ||
|
|
||
| const res = await pg.query("SELECT armor(digest('test', 'sha1')) as value;") | ||
| expect(res.rows[0].value).toContain('-----BEGIN PGP MESSAGE-----') | ||
| expect(res.rows[0].value).toContain('-----END PGP MESSAGE-----') | ||
| }) | ||
|
|
||
| it('pgp_sym_encrypt and pgp_sym_decrypt', async () => { | ||
| const pg = new PGlite({ | ||
| extensions: { | ||
| pgcrypto, | ||
| }, | ||
| }) | ||
|
|
||
| await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') | ||
|
|
||
| const res = await pg.query( | ||
| "SELECT pgp_sym_encrypt('test', 'key') as value;", | ||
| ) | ||
| const encrypted = res.rows[0].value | ||
|
|
||
| const res2 = await pg.query("SELECT pgp_sym_decrypt($1, 'key') as value;", [ | ||
| encrypted, | ||
| ]) | ||
| expect(res2.rows[0].value).toEqual('test') | ||
| }) | ||
|
|
||
| it('pgp_pub_encrypt and pgp_pub_decrypt', async () => { | ||
| const pg = new PGlite({ | ||
| extensions: { | ||
| pgcrypto, | ||
| }, | ||
| }) | ||
|
|
||
| await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') | ||
|
|
||
| const { privateKey, publicKey } = await openpgp.generateKey({ | ||
| type: 'rsa', | ||
| rsaBits: 2048, | ||
| userIDs: [{ name: 'PGlite', email: 'hello@pglite.dev' }], | ||
| passphrase: '', | ||
| }) | ||
|
|
||
| const toEncrypt = 'PGlite@$#%!^$&*WQFgjqPkVERewfreg094340f1012-=' | ||
|
|
||
| const e2 = await pg.exec( | ||
| ` | ||
| WITH encrypted AS ( | ||
| SELECT pgp_pub_encrypt('${toEncrypt}', dearmor('${publicKey}')) AS encrypted | ||
| ) | ||
| SELECT | ||
| pgp_pub_decrypt(encrypted, dearmor('${privateKey}')) as decrypted_output | ||
| FROM encrypted; | ||
| `, | ||
| ) | ||
| expect(e2[0].rows[0].decrypted_output, toEncrypt) | ||
| }) | ||
|
|
||
| it('pgp_key_id', async () => { | ||
| const pg = new PGlite({ | ||
| extensions: { | ||
| pgcrypto, | ||
| }, | ||
| }) | ||
|
|
||
| await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') | ||
|
|
||
| const { publicKey } = await openpgp.generateKey({ | ||
| type: 'rsa', | ||
| rsaBits: 2048, | ||
| userIDs: [{ name: 'PGlite', email: 'hello@pglite.dev' }], | ||
| passphrase: '', | ||
| }) | ||
|
|
||
| const res = await pg.query( | ||
| `SELECT pgp_key_id(dearmor('${publicKey}')) as value;`, | ||
| ) | ||
| // pgp_key_id returns a 16-character hex string | ||
| expect(res.rows[0].value).toHaveLength(16) | ||
| expect(res.rows[0].value).toMatch(/^[0-9A-F]+$/) | ||
| }) | ||
|
|
||
| it('pgp_armor_headers', async () => { | ||
| const pg = new PGlite({ | ||
| extensions: { | ||
| pgcrypto, | ||
| }, | ||
| }) | ||
|
|
||
| await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') | ||
|
|
||
| // Create armored data with headers | ||
| const res = await pg.query( | ||
| `SELECT armor(digest('test', 'sha1'), ARRAY['key1'], ARRAY['value1']) as armored;`, | ||
| ) | ||
| const armored = res.rows[0].armored | ||
|
|
||
| const res2 = await pg.query(`SELECT * FROM pgp_armor_headers($1);`, [ | ||
| armored, | ||
| ]) | ||
| expect(res2.rows).toContainEqual({ key: 'key1', value: 'value1' }) | ||
| }) | ||
|
|
||
| it('encrypt and decrypt', async () => { | ||
| const pg = new PGlite({ | ||
| extensions: { | ||
| pgcrypto, | ||
| }, | ||
| }) | ||
|
|
||
| await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') | ||
|
|
||
| const res = await pg.query( | ||
| `SELECT encrypt('test data'::bytea, 'secret key'::bytea, 'aes') as encrypted;`, | ||
| ) | ||
| const encrypted = res.rows[0].encrypted | ||
|
|
||
| const res2 = await pg.query( | ||
| `SELECT convert_from(decrypt($1, 'secret key'::bytea, 'aes'), 'UTF8') as decrypted;`, | ||
| [encrypted], | ||
| ) | ||
| expect(res2.rows[0].decrypted).toEqual('test data') | ||
| }) | ||
|
|
||
| it('encrypt_iv and decrypt_iv', async () => { | ||
| const pg = new PGlite({ | ||
| extensions: { | ||
| pgcrypto, | ||
| }, | ||
| }) | ||
|
|
||
| await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') | ||
|
|
||
| // AES block size is 16 bytes, so IV must be 16 bytes | ||
| const iv = '1234567890123456' | ||
|
|
||
| const res = await pg.query( | ||
| `SELECT encrypt_iv('test data'::bytea, 'secret key'::bytea, '${iv}'::bytea, 'aes') as encrypted;`, | ||
| ) | ||
| const encrypted = res.rows[0].encrypted | ||
|
|
||
| const res2 = await pg.query( | ||
| `SELECT convert_from(decrypt_iv($1, 'secret key'::bytea, '${iv}'::bytea, 'aes'), 'UTF8') as decrypted;`, | ||
| [encrypted], | ||
| ) | ||
| expect(res2.rows[0].decrypted).toEqual('test data') | ||
| }) | ||
|
|
||
| it('gen_random_bytes', async () => { | ||
| const pg = new PGlite({ | ||
| extensions: { | ||
| pgcrypto, | ||
| }, | ||
| }) | ||
|
|
||
| await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') | ||
|
|
||
| const res = await pg.query( | ||
| `SELECT length(gen_random_bytes(32)) as len, encode(gen_random_bytes(16), 'hex') as bytes;`, | ||
| ) | ||
| expect(res.rows[0].len).toEqual(32) | ||
| // 16 bytes = 32 hex characters | ||
| expect(res.rows[0].bytes).toHaveLength(32) | ||
| }) | ||
|
|
||
| it('gen_random_uuid', async () => { | ||
| const pg = new PGlite({ | ||
| extensions: { | ||
| pgcrypto, | ||
| }, | ||
| }) | ||
|
|
||
| await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') | ||
|
|
||
| const res = await pg.query(`SELECT gen_random_uuid() as uuid;`) | ||
| // UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | ||
| expect(res.rows[0].uuid).toMatch( | ||
| /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, | ||
| ) | ||
| }) | ||
| }) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect test!