├── .clang-format ├── .dockerignore ├── .editorconfig ├── .github └── workflows │ ├── jekyll-gh-pages.yml │ ├── pages.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .vscode └── c_cpp_properties.json ├── Dockerfile ├── Dockerfile-debug ├── LICENSE ├── META.json ├── Makefile ├── README.md ├── build ├── pgsodium.vcxproj └── windows.md ├── docgen.py ├── doctestify.py ├── example-psql.sh ├── example ├── Auth.ipynb ├── Box.ipynb ├── Dockerfile ├── PgSodiumAnonymizer.ipynb ├── SecretBox.ipynb ├── encrypted_column.sql ├── pgsodium_anonymizer.sql └── tce.sql ├── extension ├── pgsodium--1.0.0--1.1.0.sql ├── pgsodium--1.0.0.sql ├── pgsodium--1.1.0--1.1.1.sql ├── pgsodium--1.1.1--1.2.0.sql ├── pgsodium--1.2.0--2.0.0.sql ├── pgsodium--2.0.0--2.0.1.sql ├── pgsodium--2.0.1--2.0.2.sql ├── pgsodium--2.0.2--3.0.0.sql ├── pgsodium--3.0.0--3.0.2.sql ├── pgsodium--3.0.2--3.0.3.sql ├── pgsodium--3.0.3--3.0.4.sql ├── pgsodium--3.0.4--3.0.5.sql ├── pgsodium--3.0.5--3.0.6.sql ├── pgsodium--3.0.6--3.0.7.sql ├── pgsodium--3.0.7--3.1.0.sql ├── pgsodium--3.1.0--3.1.1.sql ├── pgsodium--3.1.1--3.1.2.sql ├── pgsodium--3.1.2--3.1.3.sql ├── pgsodium--3.1.3--3.1.4.sql ├── pgsodium--3.1.4--3.1.5.sql ├── pgsodium--3.1.5--3.1.6.sql ├── pgsodium--3.1.6--3.1.7.sql ├── pgsodium--3.1.7--3.1.8.sql └── pgsodium--3.1.8--3.1.9.sql ├── getkey_scripts ├── pgsodium_getkey.bat ├── pgsodium_getkey_aws.sh ├── pgsodium_getkey_doppler.sh ├── pgsodium_getkey_gcp.sh ├── pgsodium_getkey_urandom.sh └── pgsodium_getkey_zmk.sh ├── indent.sh ├── latest-changes.md ├── pgsodium.control ├── pgsodium_tapgen.pl ├── psql.sh ├── sql ├── box.sql ├── derive.sql ├── hash.sql ├── hmac.sql ├── kdf.sql ├── kx.sql ├── pwhash.sql ├── random.sql ├── secretbox.sql └── tce.sql ├── src ├── aead.c ├── auth.c ├── box.c ├── crypto_aead_det_xchacha20.c ├── crypto_aead_det_xchacha20.h ├── derive.c ├── hash.c ├── helpers.c ├── hmac.c ├── kdf.c ├── kx.c ├── pgsodium.c ├── pgsodium.h ├── pwhash.c ├── random.c ├── secretbox.c ├── secretstream.c ├── sha.c ├── sign.c ├── signcrypt.c ├── signcrypt_tbsbr.c ├── signcrypt_tbsbr.h └── stream.c ├── test.sh └── test ├── aead.sql ├── auth.sql ├── box.sql ├── derive.sql ├── hash.sql ├── helpers.sql ├── hmac.sql ├── kdf.sql ├── keys.sql ├── kx.sql ├── pgsodium_schema.sql ├── pwhash.sql ├── random.sql ├── secretbox.sql ├── secretstream.sql ├── sha2.sql ├── sign.sql ├── signcrypt.sql ├── stream.sql ├── tce.sql ├── tce_rls.sql └── test.sql /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | SortIncludes: 'false' 3 | IndentWidth: 4 4 | BinPackArguments: 'false' 5 | ... 6 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | test.sh 3 | psql.sh 4 | .dockerignore 5 | .git 6 | .cache 7 | test/ 8 | example/ 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 4 -------------------------------------------------------------------------------- /.github/workflows/jekyll-gh-pages.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a Jekyll site to GitHub Pages 2 | name: Deploy Jekyll with GitHub Pages dependencies preinstalled 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow one concurrent deployment 19 | concurrency: 20 | group: "pages" 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | # Build job 25 | build: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v3 30 | - name: Setup Pages 31 | uses: actions/configure-pages@v2 32 | - name: Build with Jekyll 33 | uses: actions/jekyll-build-pages@v1 34 | with: 35 | source: ./docs 36 | destination: ./_site 37 | - name: Upload artifact 38 | uses: actions/upload-pages-artifact@v1 39 | 40 | # Deployment job 41 | deploy: 42 | environment: 43 | name: github-pages 44 | url: ${{ steps.deployment.outputs.page_url }} 45 | runs-on: ubuntu-latest 46 | needs: build 47 | steps: 48 | - name: Deploy to GitHub Pages 49 | id: deployment 50 | uses: actions/deploy-pages@v1 51 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a Jekyll site to GitHub Pages 2 | name: Deploy Jekyll with GitHub Pages dependencies preinstalled 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["master"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow one concurrent deployment 19 | concurrency: 20 | group: "pages" 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | # Build job 25 | build: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v3 30 | - name: Setup Pages 31 | uses: actions/configure-pages@v2 32 | - name: Build with Jekyll 33 | uses: actions/jekyll-build-pages@v1 34 | with: 35 | source: ./docs 36 | destination: ./_site 37 | - name: Upload artifact 38 | uses: actions/upload-pages-artifact@v1 39 | 40 | # Deployment job 41 | deploy: 42 | environment: 43 | name: github-pages 44 | url: ${{ steps.deployment.outputs.page_url }} 45 | runs-on: ubuntu-latest 46 | needs: build 47 | steps: 48 | - name: Deploy to GitHub Pages 49 | id: deployment 50 | uses: actions/deploy-pages@v1 51 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: [v*] 5 | jobs: 6 | release: 7 | name: Release on GitHub and PGXN 8 | runs-on: ubuntu-latest 9 | container: pgxn/pgxn-tools 10 | env: 11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 12 | PGXN_USERNAME: ${{ secrets.PGXN_USERNAME }} 13 | PGXN_PASSWORD: ${{ secrets.PGXN_PASSWORD }} 14 | steps: 15 | - name: Check out the repo 16 | uses: actions/checkout@v2 17 | - name: Bundle the Release 18 | id: bundle 19 | run: pgxn-bundle 20 | - name: Release on PGXN 21 | run: pgxn-release 22 | # - name: Create GitHub Release 23 | # id: release 24 | # uses: actions/create-release@v1 25 | # with: 26 | # tag_name: ${{ github.ref }} 27 | # release_name: Release ${{ github.ref }} 28 | # body_path: latest-changes.md 29 | # - name: Upload Release Asset 30 | # uses: actions/upload-release-asset@v1 31 | # with: 32 | # upload_url: ${{ steps.release.outputs.upload_url }} 33 | # asset_path: ./${{ steps.bundle.outputs.bundle }} 34 | # asset_name: ${{ steps.bundle.outputs.bundle }} 35 | # asset_content_type: application/zip 36 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test_linux: 7 | name: Linux build and tests 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout source code 11 | uses: actions/checkout@v2 12 | 13 | - name: Run tests 14 | run: | 15 | ./test.sh 16 | test_windows: 17 | name: Windows build and tests 18 | runs-on: windows-latest 19 | steps: 20 | - name: Checkout source code 21 | uses: actions/checkout@v2 22 | 23 | - name: Add msbuild to PATH 24 | uses: microsoft/setup-msbuild@v1 25 | 26 | - name: Install dependencies 27 | run: | 28 | Invoke-WebRequest -URI https://download.libsodium.org/libsodium/releases/libsodium-1.0.18-stable-msvc.zip -OutFile libsodium-1.0.18-stable-msvc.zip 29 | tar -xf libsodium-1.0.18-stable-msvc.zip 30 | rm libsodium-1.0.18-stable-msvc.zip 31 | cp .\libsodium\x64\Release\v143\dynamic\libsodium.dll $env:PGROOT\lib 32 | Invoke-WebRequest -URI https://github.com/theory/pgtap/archive/refs/tags/v1.2.0.zip -OutFile pgtap.zip 33 | tar -xf pgtap.zip 34 | rm pgtap.zip 35 | cd pgtap-1.2.0 36 | cp .\sql\pgtap.sql.in .\sql\pgtap.sql 37 | perl.exe '-pi.bak' -e "s/TAPSCHEMA/tap/g" .\sql\pgtap.sql 38 | perl.exe '-pi.bak' -e "s/__OS__/win32/g" .\sql\pgtap.sql 39 | perl.exe '-pi.bak' -e "s/__VERSION__/0.24/g" .\sql\pgtap.sql 40 | perl.exe '-pi.bak' -e "s/^-- ## //g" .\sql\pgtap.sql 41 | cp .\sql\pgtap.sql $env:PGROOT\share\extension 42 | cp .\pgtap.control $env:PGROOT\share\extension 43 | cp .\contrib\pgtap.spec $env:PGROOT\share\contrib 44 | ren $env:PGROOT\share\extension\pgtap.sql $env:PGROOT\share\extension\pgtap--1.2.0.sql 45 | shell: pwsh 46 | 47 | - name: Run msbuild 48 | working-directory: ./build 49 | run: | 50 | msbuild pgsodium.vcxproj /p:libsodiumLocation=..\libsodium /p:PostgreSQLLocation=%PGROOT% /p:Configuration=Release /p:Platform=x64 /p:platformToolset=v143 51 | 52 | - name: Install pgsodium, update config, and restart 53 | run: | 54 | cp .\build\x64\Release\pgsodium.dll $env:PGROOT\lib 55 | cp pgsodium.control $env:PGROOT\share\extension 56 | cp .\sql\* $env:PGROOT\share\extension 57 | cp .\getkey_scripts\pgsodium_getkey.bat $env:PGDATA\ 58 | ((Get-Content -Path $env:PGDATA\postgresql.conf) -Replace "#shared_preload_libraries = ''","shared_preload_libraries = 'pgsodium'") | Set-Content -Path $env:PGDATA\postgresql.conf 59 | Add-Content -Path $env:PGDATA\postgresql.conf -Value ("pgsodium.getkey_script = '$env:PGDATA\pgsodium_getkey.bat'" -Replace "\\","/") 60 | & $env:PGBIN\pg_ctl restart -D $env:PGDATA 61 | shell: pwsh 62 | 63 | - name: Run pgsodium tests 64 | run: | 65 | & $env:PGBIN\psql -q -U postgres -f .\test\test.sql 66 | shell: pwsh 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | *.su 34 | *~ 35 | 36 | # Visual Studio Build files 37 | build/.vs/ 38 | *.vcxproj.user 39 | build/x64/ 40 | 41 | pgsodium_encrypted_root.key 42 | .ipynb_checkpoints/ -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "/usr/include/postgresql/**" 8 | ], 9 | "defines": [], 10 | "compilerPath": "/usr/bin/clang", 11 | "cStandard": "c17", 12 | "cppStandard": "c++14", 13 | "intelliSenseMode": "linux-clang-x64" 14 | } 15 | ], 16 | "version": 4 17 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest 2 | ARG version 3 | ARG DEBIAN_FRONTEND=noninteractive 4 | 5 | # install base dependences 6 | RUN apt-get update && \ 7 | apt-get install -y make cmake git curl build-essential m4 sudo gdbserver \ 8 | gdb libreadline-dev bison flex zlib1g-dev tmux zile zip vim gawk wget libicu-dev pkg-config 9 | 10 | # add postgres user and make data dir 11 | RUN groupadd -r postgres && useradd --no-log-init -r -m -s /bin/bash -g postgres -G sudo postgres 12 | RUN echo "postgres ALL=(root) NOPASSWD:ALL" > /etc/sudoers.d/user && \ 13 | chmod 0440 /etc/sudoers.d/user 14 | 15 | ENV PGDATA /home/postgres/data 16 | RUN /bin/rm -Rf "$PGDATA" && mkdir "$PGDATA" 17 | WORKDIR "/home/postgres" 18 | 19 | # get postgres source and compile with debug and no optimization 20 | RUN git clone --branch REL_${version}_STABLE https://github.com/postgres/postgres.git --depth=1 && \ 21 | cd postgres && ./configure \ 22 | --prefix=/usr/ \ 23 | --enable-debug \ 24 | --enable-depend --enable-cassert --enable-profiling \ 25 | CFLAGS="-ggdb -Og -g3 -fno-omit-frame-pointer" \ 26 | # CFLAGS="-O3" \ 27 | && make -j 4 && make install 28 | 29 | RUN chown postgres:postgres /home/postgres 30 | 31 | RUN curl -s -L https://github.com/theory/pgtap/archive/v1.2.0.tar.gz | tar zxvf - && cd pgtap-1.2.0 && make && make install 32 | RUN curl -s -L https://github.com/jedisct1/libsodium/releases/download/1.0.20-RELEASE/libsodium-1.0.20.tar.gz | tar zxvf - && cd libsodium-1.0.20 && ./configure && make check && make -j 4 install 33 | RUN cpan App::cpanminus && cpan TAP::Parser::SourceHandler::pgTAP && cpan App::prove 34 | 35 | RUN git clone --depth 1 https://github.com/lacanoid/pgddl.git 36 | RUN cd pgddl && make && make install && cd .. 37 | 38 | RUN mkdir "/home/postgres/pgsodium" 39 | WORKDIR "/home/postgres/pgsodium" 40 | COPY . . 41 | RUN make -j 4 && make install 42 | RUN ldconfig 43 | RUN cd `pg_config --sharedir`/extension/ 44 | RUN cp getkey_scripts/pgsodium_getkey_urandom.sh `pg_config --sharedir`/extension/pgsodium_getkey 45 | RUN sed -i 's/exit//g' `pg_config --sharedir`/extension/pgsodium_getkey 46 | RUN chmod +x `pg_config --sharedir`/extension/pgsodium_getkey 47 | RUN cp `pg_config --sharedir`/extension/pgsodium_getkey /getkey 48 | 49 | RUN chown -R postgres:postgres /home/postgres/pgsodium 50 | RUN chown -R postgres:postgres /home/postgres/data 51 | 52 | # make postgres a sudoer 53 | RUN echo "postgres ALL=(root) NOPASSWD:ALL" > /etc/sudoers.d/user && \ 54 | chmod 0440 /etc/sudoers.d/user 55 | 56 | # start the database 57 | USER postgres 58 | RUN initdb -D "$PGDATA" 59 | EXPOSE 5432 60 | CMD ["/usr/bin/postgres"] 61 | -------------------------------------------------------------------------------- /Dockerfile-debug: -------------------------------------------------------------------------------- 1 | ARG version 2 | FROM postgres:${version} 3 | ARG version 4 | 5 | # RUN apt-get update && apt-get install -y make git postgresql-server-dev-${version} curl build-essential gdb 6 | RUN apt-get update && apt-get install -y make git curl build-essential gdb libreadline-dev bison flex zlib1g-dev tmux zile zip gawk 7 | 8 | RUN git clone --branch REL_${version}_STABLE https://github.com/postgres/postgres.git --depth=1 && \ 9 | cd postgres && ./configure \ 10 | --prefix=/usr/ \ 11 | --enable-debug \ 12 | --enable-depend --enable-cassert --enable-profiling \ 13 | CFLAGS="-ggdb -Og -g3 -fno-omit-frame-pointer" \ 14 | # CFLAGS="-O3" \ 15 | && make -j 4 && make install 16 | 17 | RUN curl -s -L https://github.com/theory/pgtap/archive/v1.1.0.tar.gz | tar zxvf - && cd pgtap-1.1.0 && make && make install 18 | RUN curl -s -L https://download.libsodium.org/libsodium/releases/libsodium-1.0.18.tar.gz | tar zxvf - && cd libsodium-1.0.18 && ./configure && make check && make install 19 | RUN mkdir "/pgsodium" 20 | WORKDIR "/pgsodium" 21 | COPY . . 22 | RUN make && make install 23 | RUN ldconfig 24 | RUN curl -O https://raw.githubusercontent.com/tvondra/gdbpg/master/gdbpg.py 25 | RUN cd `pg_config --sharedir`/extension/ 26 | RUN cp getkey_scripts/pgsodium_getkey_urandom.sh `pg_config --sharedir`/extension/pgsodium_getkey 27 | RUN sed -i 's/exit//g' `pg_config --sharedir`/extension/pgsodium_getkey 28 | RUN chmod +x `pg_config --sharedir`/extension/pgsodium_getkey 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # pgsodium License 2 | 3 | Copyright (c) 2020 Michel Pelletier and Contributors 4 | 5 | Permission to use, copy, modify, and distribute this software and its 6 | documentation for any purpose, without fee, and without a written agreement 7 | is hereby granted, provided that the above copyright notice and this 8 | paragraph and the following two paragraphs appear in all copies. 9 | 10 | IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR 11 | DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING 12 | LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS 13 | DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE 14 | POSSIBILITY OF SUCH DAMAGE. 15 | 16 | THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 17 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 18 | AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS 19 | ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO 20 | PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 21 | 22 | # libsodium-xchacha20-siv License 23 | 24 | BSD 2-Clause License 25 | 26 | Copyright (c) 2020, Frank Denis 27 | All rights reserved. 28 | 29 | Redistribution and use in source and binary forms, with or without 30 | modification, are permitted provided that the following conditions are met: 31 | 32 | * Redistributions of source code must retain the above copyright notice, this 33 | list of conditions and the following disclaimer. 34 | 35 | * Redistributions in binary form must reproduce the above copyright notice, 36 | this list of conditions and the following disclaimer in the documentation 37 | and/or other materials provided with the distribution. 38 | 39 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 40 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 41 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 42 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 43 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 44 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 45 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 46 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 47 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 48 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 49 | 50 | # libsodium-signcryption License 51 | 52 | MIT License 53 | 54 | Copyright (c) 2020-2021 Frank Denis 55 | 56 | Permission is hereby granted, free of charge, to any person obtaining a copy 57 | of this software and associated documentation files (the "Software"), to deal 58 | in the Software without restriction, including without limitation the rights 59 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 60 | copies of the Software, and to permit persons to whom the Software is 61 | furnished to do so, subject to the following conditions: 62 | 63 | The above copyright notice and this permission notice shall be included in all 64 | copies or substantial portions of the Software. 65 | 66 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 67 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 68 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 69 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 70 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 71 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 72 | SOFTWARE. -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pgsodium", 3 | "abstract": "Postgres extension for libsodium functions", 4 | "description": "pgsodium is a PostgreSQL extension that exposes modern libsodium based cryptographic functions to SQL.", 5 | "version": "3.1.9", 6 | "maintainer": [ 7 | "Michel Pelletier " 8 | ], 9 | "release_status": "stable", 10 | "license": "postgresql", 11 | "provides": { 12 | "pgsodium": { 13 | "abstract": "Postgres extension for libsodium functions", 14 | "file": "src/pgsodium.h", 15 | "docfile": "README.md", 16 | "version": "3.1.9" 17 | } 18 | }, 19 | "prereqs": { 20 | "runtime": { 21 | "requires": { 22 | "PostgreSQL": "11.0.0" 23 | } 24 | } 25 | }, 26 | "resources": { 27 | "bugtracker": { 28 | "web": "https://github.com/michelp/pgsodium/issues/" 29 | }, 30 | "repository": { 31 | "url": "git://github.com/michelp/pgsodium.git", 32 | "web": "https://github.com/michelp/pgsodium/", 33 | "type": "git" 34 | } 35 | }, 36 | "generated_by": "David E. Wheeler", 37 | "meta-spec": { 38 | "version": "1.0.0", 39 | "url": "https://pgxn.org/meta/spec.txt" 40 | }, 41 | "tags": [ 42 | "sodium", 43 | "crypto", 44 | "cryptography", 45 | "encryption", 46 | "random", 47 | "asymmetric encryption", 48 | "public key" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #http://blog.pgxn.org/post/4783001135/extension-makefiles pg makefiles 2 | 3 | EXTENSION = pgsodium 4 | PG_CONFIG ?= pg_config 5 | DATA = $(wildcard extension/*--*.sql) 6 | PGXS := $(shell $(PG_CONFIG) --pgxs) 7 | MODULE_big = pgsodium 8 | OBJS = $(patsubst %.c,%.o,$(wildcard src/*.c)) 9 | SHLIB_LINK = -lsodium 10 | PG_CPPFLAGS = -O0 11 | 12 | TESTS = $(wildcard sql/*.sql) 13 | REGRESS = $(patsubst sql/%.sql,%,$(TESTS)) 14 | include $(PGXS) 15 | 16 | dist: 17 | git archive --format zip --prefix=$(EXTENSION)-$(DISTVERSION)/ -o $(EXTENSION)-$(DISTVERSION).zip HEAD 18 | -------------------------------------------------------------------------------- /build/pgsodium.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win64 7 | 8 | 9 | Release 10 | Win64 11 | 12 | 13 | 14 | 15 | 16 | 17 | x64 18 | DynamicLibrary 19 | true 20 | pgsodium 21 | 10.0 22 | $(platformToolset) 23 | Unicode 24 | 25 | 26 | 27 | true 28 | false 29 | false 30 | Level3 31 | true 32 | 33 | 34 | 35 | true 36 | true 37 | false 38 | All 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | false 75 | true 76 | CompileAsC 77 | 78 | $(PostgreSQLLocation)\include\server\port\win32_msvc; 79 | $(PostgreSQLLocation)\include\server\port\win32; 80 | $(PostgreSQLLocation)\include\server; 81 | $(PostgreSQLLocation)\include; 82 | $(libsodiumLocation)\include; 83 | %(AdditionalIncludeDirectories) 84 | 85 | stdcpp17 86 | 87 | 88 | 89 | Console 90 | true 91 | 92 | $(libsodiumLocation)\x64\Release\$(platformToolset)\dynamic; 93 | $(PostgreSQLLocation)\lib; 94 | %(AdditionalLibraryDirectories) 95 | 96 | 97 | postgres.lib; 98 | libsodium.lib; 99 | %(AdditionalDependencies) 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /build/windows.md: -------------------------------------------------------------------------------- 1 | #### Building on Windows 2 | --------- 3 | 4 | - Download [libsodium](https://download.libsodium.org/libsodium/releases/libsodium-1.0.18-stable-msvc.zip) >= 1.018 and unzip 5 | - Download and run the [postgresql installer](https://www.postgresql.org/download/windows/) 6 | - From the `/pgsodium/build` directory, run `msbuild` on `pgsodium.vcxproj` 7 | - `msbuild` can be invoked though the *x64 Native Tools Command Prompt for VS 2022* 8 | 9 | The following properties ( **`/p`** or **`/property`** ) must be specified: 10 | - `libsodiumLocation`: root libsodium directory 11 | - `PostgreSQLLocation`: root postgresql directory, typically `C:\Program Files\PostgreSQL\ 12 | - `Configuration`: [`Release`, `Debug`] 13 | - `Platform`: [x64] 14 | - `platformToolset`: [`v142`, `v143`] 15 | 16 | ie. 17 | 18 | ``` 19 | msbuild pgsodium.vcxproj /p:libsodiumLocation="C:\libsodium" /p:PostgreSQLLocation="C:\Program Files\PostgreSQL\15" /p:Configuration=Release /p:Platform=x64 /p:platformToolset=v143 20 | ``` 21 | 22 | - Copy the `libsodium.dll` (from `libsodium\x64\Release\\dynamic`) and the newly built `pgsodium.dll` into your `\PostgreSQL\\lib` directory -------------------------------------------------------------------------------- /docgen.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | 5 | def process_file(path): 6 | with open(path, "r") as f: 7 | content = f.read() 8 | s = content.lstrip() 9 | if not s.startswith("/* doctest"): 10 | return 11 | end = s.find("*/") 12 | if end == -1: 13 | return 14 | comment = s[: end + 2] 15 | lines = comment.splitlines() 16 | if not lines: 17 | return 18 | first = lines[0] 19 | if not first.startswith("/* doctest"): 20 | return 21 | target = first[len("/* doctest"):].strip() 22 | if target.startswith("/"): 23 | target = target[1:] 24 | if not target: 25 | return 26 | target = target.replace('/', '_') 27 | stripped_comment = "\n".join(lines[1:-1]).strip() if len(lines) >= 3 else "" 28 | out_file = os.path.join("sql", target + ".sql") 29 | os.makedirs(os.path.dirname(out_file), exist_ok=True) 30 | with open(out_file, "w") as out: 31 | out.write(stripped_comment + "\n\n") 32 | 33 | 34 | def main(): 35 | base = sys.argv[1] if len(sys.argv) > 1 else "." 36 | for root, _, files in os.walk(base): 37 | for f in files: 38 | if f.endswith(".c"): 39 | process_file(os.path.join(root, f)) 40 | 41 | 42 | if __name__ == "__main__": 43 | main() 44 | -------------------------------------------------------------------------------- /doctestify.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | VERSION = '0.1.0' 4 | 5 | def doctestify(test): 6 | lines = test.splitlines() 7 | markdown_lines = [] 8 | in_code_block = False 9 | 10 | for line in lines: 11 | if line.startswith("\\") or "\\gset" in line or "-- pragma:hide" in line: 12 | continue 13 | if line.startswith("--") and not line.startswith("---"): 14 | if in_code_block: 15 | markdown_lines.append("```") 16 | in_code_block = False 17 | markdown_lines.append(line[3:]) 18 | else: 19 | if not in_code_block: 20 | markdown_lines.append("``` postgres-console") 21 | in_code_block = True 22 | line = line.replace("\u21B5", " ") 23 | markdown_lines.append(line) 24 | 25 | if in_code_block: 26 | markdown_lines.append("```") 27 | 28 | return "\n".join(markdown_lines) 29 | 30 | if __name__ == '__main__': 31 | import sys 32 | test = Path(sys.argv[1]) 33 | infile = open(test, 'r') 34 | doc = Path('docs', *test.with_suffix('.md').parts[1:]) 35 | outfile = open(doc, 'w+') 36 | outfile.write(doctestify(infile.read())) 37 | -------------------------------------------------------------------------------- /example-psql.sh: -------------------------------------------------------------------------------- 1 | version=${1:-13} 2 | shift 3 | 4 | DB_HOST="pgsodium-example-db-$version" 5 | DB_NAME="postgres" 6 | SU="postgres" 7 | EXEC="docker exec $DB_HOST" 8 | TAG="pgsodium/test-$version" 9 | 10 | echo building test image $DB_HOST 11 | docker build -f example/Dockerfile . -t $TAG --build-arg "version=$version" 12 | 13 | echo running test container 14 | docker run -v `pwd`/example:/pgsodium/example -e POSTGRES_HOST_AUTH_METHOD=trust -d --name "$DB_HOST" $TAG -c 'shared_preload_libraries=pgsodium,anon' 15 | 16 | echo waiting for database to accept connections 17 | until 18 | $EXEC \ 19 | psql -o /dev/null -t -q -U "$SU" \ 20 | -c 'select pg_sleep(1)' \ 21 | 2>/dev/null; 22 | do sleep 1; 23 | done 24 | 25 | docker exec -it $DB_HOST psql -U "$SU" $@ 26 | -------------------------------------------------------------------------------- /example/Auth.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Authentication\n", 8 | "\n", 9 | "The `auth` API uses cryptographic authenticated to verify a message has not been altered with a tag and secret key that all parties must share.\n", 10 | "\n", 11 | "[Libsodium Documentation](https://doc.libsodium.org/secret-key_cryptography/secret-key_authentication)\n", 12 | "\n", 13 | "Functions:\n", 14 | "```\n", 15 | " crypto_auth_keygen() -> bytea\n", 16 | "\n", 17 | " crypto_auth(message bytea, key bytea) -> bytea\n", 18 | "\n", 19 | " crypto_auth(message bytea, key_id bigint, context bytea = 'pgsodium') -> bytea\n", 20 | "\n", 21 | " crypto_auth_verify(mac bytea, message bytea, key bytea) -> boolean\n", 22 | "\n", 23 | " crypto_auth_verify(mac bytea, message bytea, key_id bigint, context bytea = 'pgsodium') -> boolean\n", 24 | "```\n", 25 | "\n", 26 | "`crypto_auth_keygen()` generates a message-signing key for use by\n", 27 | "`crypto_auth()`. The role `pgsodium_keymaker` is required to call this function.\n", 28 | "\n", 29 | "`crypto_auth()` generates an authentication tag (mac) for a\n", 30 | "combination of message and secret key. This does not encrypt the\n", 31 | "message; it simply provides a means to prove that the message has not\n", 32 | "been tampered with. To verify a message tagged in this way, use\n", 33 | "`crypto_auth_verify()`. This function is deterministic: for a given\n", 34 | "message and key, the generated mac will always be the same. The role `pgsodium_keyholder` is required to call the raw `key bytea` versions of these functions. The key id versions of the functions can be called with the role `pgsodium_keyiduser`.\n", 35 | "\n", 36 | "Note that this requires access to the secret\n", 37 | "key, which is not something that should normally be shared. If\n", 38 | "many users need to verify message it is usually better to use\n", 39 | "[Public Key Signatures](#user-content-public-key-signatures) rather\n", 40 | "than sharing secret keys.\n" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": 1, 46 | "metadata": { 47 | "scrolled": true 48 | }, 49 | "outputs": [], 50 | "source": [ 51 | "%load_ext sql\n", 52 | "%config SqlMagic.feedback=False\n", 53 | "%config SqlMagic.displaycon=False\n", 54 | "%sql postgresql://postgres@/" 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "metadata": {}, 60 | "source": [ 61 | "Encryption requires a key and a nonce. The nonce doesn't have to be confidential, but it should never ever be reused with the same key. The easiest way to generate a nonce is to use `crypto_secretbox_noncegen`:" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": 2, 67 | "metadata": {}, 68 | "outputs": [], 69 | "source": [ 70 | "key = %sql select pgsodium.crypto_auth_keygen()::text\n", 71 | "key = key[0][0]" 72 | ] 73 | }, 74 | { 75 | "cell_type": "markdown", 76 | "metadata": {}, 77 | "source": [ 78 | "## Signing\n", 79 | "\n", 80 | "A new signature is created with the message and the key:" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": 3, 86 | "metadata": { 87 | "scrolled": true 88 | }, 89 | "outputs": [ 90 | { 91 | "name": "stdout", 92 | "output_type": "stream", 93 | "text": [ 94 | "The signature is: \\x048a21486e401e4899ec6229cda326af564c41cd1b9baad57a41c3e78aaed752\n" 95 | ] 96 | } 97 | ], 98 | "source": [ 99 | "signature = %sql SELECT crypto_auth::text from pgsodium.crypto_auth('bob is your uncle', (:key)::bytea)\n", 100 | "signature = signature[0][0]\n", 101 | "print('The signature is: ', signature)" 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "metadata": {}, 107 | "source": [ 108 | "## Verification\n", 109 | "\n", 110 | "Using the tag and key, verify the authenticity of the message." 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": 4, 116 | "metadata": {}, 117 | "outputs": [ 118 | { 119 | "data": { 120 | "text/html": [ 121 | "\n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | "
crypto_auth_verify
True
" 129 | ], 130 | "text/plain": [ 131 | "[(True,)]" 132 | ] 133 | }, 134 | "execution_count": 4, 135 | "metadata": {}, 136 | "output_type": "execute_result" 137 | } 138 | ], 139 | "source": [ 140 | "%sql SELECT crypto_auth_verify FROM pgsodium.crypto_auth_verify(:signature, 'bob is your uncle', (:key)::bytea)\n" 141 | ] 142 | } 143 | ], 144 | "metadata": { 145 | "kernelspec": { 146 | "display_name": "Python 3", 147 | "language": "python", 148 | "name": "python3" 149 | }, 150 | "language_info": { 151 | "codemirror_mode": { 152 | "name": "ipython", 153 | "version": 3 154 | }, 155 | "file_extension": ".py", 156 | "mimetype": "text/x-python", 157 | "name": "python", 158 | "nbconvert_exporter": "python", 159 | "pygments_lexer": "ipython3", 160 | "version": "3.9.2" 161 | } 162 | }, 163 | "nbformat": 4, 164 | "nbformat_minor": 2 165 | } 166 | -------------------------------------------------------------------------------- /example/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG version 2 | FROM postgres:${version} 3 | ARG version 4 | 5 | RUN apt-get update && apt-get install -y make git postgresql-server-dev-${version} curl build-essential libreadline-dev pgxnclient python3-notebook jupyter jupyter-core python3-pip 6 | RUN curl -s -L https://download.libsodium.org/libsodium/releases/libsodium-1.0.18.tar.gz | tar zxvf - && cd libsodium-1.0.18 && ./configure && make check && make install 7 | 8 | # RUN curl -s -L https://github.com/theory/pgtap/archive/v1.1.0.tar.gz | tar zxvf - && cd pgtap-1.1.0 && make && make install 9 | # RUN curl -s -L https://gitlab.com/dalibo/postgresql_anonymizer/-/archive/0.6.0/postgresql_anonymizer-0.6.0.tar.gz | tar zxvf - && cd postgresql_anonymizer-0.6.0 && make extension && make install 10 | 11 | RUN pgxn install postgresql_anonymizer 12 | RUN pgxn install pgtap 13 | RUN pgxn install ddlx 14 | RUN pip3 install ipython-sql sqlalchemy psycopg2 pgspecial ipykernel 15 | 16 | RUN mkdir "/pgsodium" 17 | WORKDIR "/pgsodium" 18 | COPY . . 19 | RUN make && make install 20 | RUN ldconfig 21 | RUN cd `pg_config --sharedir`/extension/ 22 | RUN cp getkey_scripts/pgsodium_getkey_urandom.sh `pg_config --sharedir`/extension/pgsodium_getkey 23 | RUN sed -i 's/exit//g' `pg_config --sharedir`/extension/pgsodium_getkey 24 | RUN chmod +x `pg_config --sharedir`/extension/pgsodium_getkey 25 | RUN chown -R postgres:postgres /pgsodium 26 | -------------------------------------------------------------------------------- /example/SecretBox.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Secret Box\n", 8 | "\n", 9 | "The `secretbox` API uses secret key authenticated encryption to encrypt and authenticate a message with a secret key that all parties must share.\n", 10 | "\n", 11 | "[Libsodium Documentation](https://doc.libsodium.org/secret-key_cryptography/secretbox)\n", 12 | "\n", 13 | "Functions:\n", 14 | "```\n", 15 | " crypto_secretbox_keygen() -> bytea\n", 16 | "\n", 17 | " crypto_secretbox_noncegen() -> bytea\n", 18 | "\n", 19 | " crypto_secretbox(message bytea, nonce bytea, key bytea) -> bytea\n", 20 | "\n", 21 | " crypto_secretbox(message bytea, nonce bytea, key_id bigint, context bytea = 'pgsodium') -> bytea\n", 22 | "\n", 23 | " crypto_secretbox_open(ciphertext bytea, nonce bytea, key bytea) -> bytea\n", 24 | "\n", 25 | " crypto_secretbox_open(ciphertext bytea, nonce bytea, key_id bigint, context bytea = 'pgsodium') -> bytea\n", 26 | "```\n", 27 | "\n", 28 | "`crypto_secretbox_keygen()` generates a random secret key which can be\n", 29 | "used to encrypt and decrypt messages. The role `pgsodium_keymaker` is required to call this function.\n", 30 | "\n", 31 | "`crypto_secretbox_noncegen()` generates a random nonce which will be\n", 32 | "used when encrypting messages. For security, each nonce must be used\n", 33 | "only once, though it is not a secret. The purpose of the nonce is to\n", 34 | "add randomness to the message so that the same message encrypted\n", 35 | "multiple times with the same key will produce different ciphertexts. The role `pgsodium_keyiduser` or greater is required to call this function.\n", 36 | "\n", 37 | "`crypto_secretbox()` encrypts a message using a previously generated\n", 38 | "nonce and secret key. The encrypted message can be decrypted using\n", 39 | "`crypto_secretbox_open()` Note that in order to decrypt the message,\n", 40 | "the original nonce will be needed. The role `pgsodium_keyholder` is required to call the raw `key bytea` versions of these functions. The key id versions of the functions can be called with the role `pgsodium_keyiduser`.\n", 41 | "\n" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 4, 47 | "metadata": { 48 | "scrolled": false 49 | }, 50 | "outputs": [ 51 | { 52 | "name": "stdout", 53 | "output_type": "stream", 54 | "text": [ 55 | "The sql extension is already loaded. To reload it, use:\n", 56 | " %reload_ext sql\n" 57 | ] 58 | } 59 | ], 60 | "source": [ 61 | "%load_ext sql\n", 62 | "%sql postgresql://postgres@/" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "metadata": {}, 68 | "source": [ 69 | "Encryption requires a key and a nonce. The nonce doesn't have to be confidential, but it should never ever be reused with the same key. The easiest way to generate a nonce is to use `crypto_secretbox_noncegen`:" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "key = %sql select pgsodium.crypto_secretbox_keygen()::text\n", 79 | "key = key[0][0]\n", 80 | "\n", 81 | "nonce = %sql select pgsodium.crypto_secretbox_noncegen()::text\n", 82 | "nonce = nonce[0][0]" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "metadata": {}, 88 | "source": [ 89 | "## Encryption\n", 90 | "\n", 91 | "A new secretbox is created with the key and the nonce:" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": 18, 97 | "metadata": { 98 | "scrolled": true 99 | }, 100 | "outputs": [ 101 | { 102 | "name": "stdout", 103 | "output_type": "stream", 104 | "text": [ 105 | " * postgresql://postgres@/\n", 106 | "1 rows affected.\n", 107 | " * postgresql://postgres@/\n", 108 | "1 rows affected.\n", 109 | " * postgresql://postgres@/\n", 110 | "1 rows affected.\n", 111 | "The encrypted secretbox is: \\x7b11d8e3659f6fe2a7762f082019c607d5d64fd5f805f6ff6df68266664a6ec335\n" 112 | ] 113 | } 114 | ], 115 | "source": [ 116 | "secretbox = %sql SELECT crypto_secretbox::text from pgsodium.crypto_secretbox('bob is your uncle', :nonce, (:key)::bytea)\n", 117 | "secretbox = secretbox[0][0]\n", 118 | "print('The encrypted secretbox is: ', secretbox)" 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "metadata": {}, 124 | "source": [ 125 | "## Decryption\n", 126 | "\n", 127 | "Decryption requires the same key and nonce." 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": 19, 133 | "metadata": {}, 134 | "outputs": [ 135 | { 136 | "name": "stdout", 137 | "output_type": "stream", 138 | "text": [ 139 | " * postgresql://postgres@/\n", 140 | "1 rows affected.\n", 141 | "The decrypted message is : bob is your uncle\n" 142 | ] 143 | } 144 | ], 145 | "source": [ 146 | "plaintext = %sql SELECT crypto_secretbox_open FROM pgsodium.crypto_secretbox_open(:secretbox, :nonce, (:key)::bytea)\n", 147 | "print('The decrypted message is :', plaintext[0][0].tobytes().decode('utf8'))" 148 | ] 149 | } 150 | ], 151 | "metadata": { 152 | "kernelspec": { 153 | "display_name": "Python 3", 154 | "language": "python", 155 | "name": "python3" 156 | }, 157 | "language_info": { 158 | "codemirror_mode": { 159 | "name": "ipython", 160 | "version": 3 161 | }, 162 | "file_extension": ".py", 163 | "mimetype": "text/x-python", 164 | "name": "python", 165 | "nbconvert_exporter": "python", 166 | "pygments_lexer": "ipython3", 167 | "version": "3.7.3" 168 | } 169 | }, 170 | "nbformat": 4, 171 | "nbformat_minor": 2 172 | } 173 | -------------------------------------------------------------------------------- /example/encrypted_column.sql: -------------------------------------------------------------------------------- 1 | CREATE SCHEMA IF NOT EXISTS pgsodium; 2 | CREATE EXTENSION IF NOT EXISTS pgsodium WITH SCHEMA pgsodium; 3 | 4 | -- This is a demonstration user to show that the pgsodium_keyiduser 5 | -- role can be used to access only encryption functions by key_id, 6 | -- this role can never access raw encryption keys. 7 | 8 | CREATE ROLE auser; 9 | GRANT pgsodium_keyiduser TO auser; 10 | GRANT USAGE ON SCHEMA pgsodium TO auser; 11 | 12 | SET ROLE auser; 13 | 14 | CREATE TABLE IF NOT EXISTS test ( 15 | id bigserial primary key, 16 | key_id bigint not null default 1, 17 | nonce bytea not null, 18 | data bytea 19 | ); 20 | 21 | CREATE OR REPLACE VIEW test_view AS 22 | SELECT id, 23 | convert_from( 24 | pgsodium.crypto_secretbox_open( 25 | data, 26 | nonce, 27 | key_id), 28 | 'utf8') AS data FROM test; 29 | 30 | CREATE OR REPLACE FUNCTION test_encrypt() RETURNS trigger 31 | language plpgsql AS 32 | $$ 33 | DECLARE 34 | new_nonce bytea = pgsodium.crypto_secretbox_noncegen(); 35 | test_id bigint; 36 | BEGIN 37 | 38 | INSERT INTO test (nonce) VALUES (new_nonce) RETURNING ID INTO test_id; 39 | UPDATE test SET 40 | data = pgsodium.crypto_secretbox( 41 | convert_to(new.data, 'utf8'), 42 | new_nonce, 43 | key_id) 44 | WHERE id = test_id; 45 | RETURN new; 46 | END; 47 | $$; 48 | 49 | CREATE TRIGGER test_encrypt_trigger 50 | INSTEAD OF INSERT ON test_view 51 | FOR EACH ROW 52 | EXECUTE FUNCTION test_encrypt(); 53 | 54 | CREATE OR REPLACE FUNCTION rotate_key(test_id bigint, new_key bigint) 55 | RETURNS void LANGUAGE plpgsql AS $$ 56 | DECLARE 57 | new_nonce bytea; 58 | BEGIN 59 | new_nonce = pgsodium.crypto_secretbox_noncegen(); 60 | UPDATE test SET 61 | nonce = new_nonce, 62 | key_id = new_key, 63 | data = pgsodium.crypto_secretbox( 64 | pgsodium.crypto_secretbox_open( 65 | test.data, 66 | test.nonce, 67 | test.key_id), 68 | new_nonce, 69 | new_key) 70 | WHERE test.id = test_id; 71 | RETURN; 72 | END; 73 | $$; 74 | \echo 75 | \echo Try inserting some data in test_view like: 76 | \echo " postgres=> insert into test_view (data) values ('this is one'), ('this is two');" 77 | \echo Type RESET ROLE; to get back to previous user 78 | 79 | insert into test_view (data) values ('this is one'), ('this is two'); 80 | -------------------------------------------------------------------------------- /example/pgsodium_anonymizer.sql: -------------------------------------------------------------------------------- 1 | DROP SCHEMA IF EXISTS pgsodium CASCADE; 2 | DROP SCHEMA IF EXISTS anon CASCADE; 3 | DROP EXTENSION IF EXISTS pgsodium CASCADE; 4 | DROP EXTENSION IF EXISTS anon CASCADE; 5 | 6 | CREATE SCHEMA pgsodium; 7 | CREATE EXTENSION pgsodium WITH SCHEMA pgsodium; 8 | CREATE EXTENSION anon CASCADE; 9 | SELECT anon.load(); 10 | 11 | DROP TABLE IF EXISTS encrypted_record CASCADE; 12 | DROP OWNED BY staff CASCADE; 13 | DROP OWNED BY crypter CASCADE; 14 | DROP ROLE IF EXISTS staff; 15 | DROP ROLE IF EXISTS crypter; 16 | CREATE ROLE staff; 17 | CREATE ROLE crypter; 18 | 19 | GRANT pgsodium_keyiduser TO crypter; 20 | GRANT USAGE ON SCHEMA pgsodium to crypter; 21 | 22 | GRANT USAGE ON SCHEMA anon TO crypter; 23 | GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA anon TO crypter, staff; 24 | GRANT SELECT ON ALL TABLES IN SCHEMA anon TO crypter, staff; 25 | GRANT USAGE ON ALL SEQUENCES IN SCHEMA anon TO crypter, staff; 26 | 27 | CREATE TABLE encrypted_record ( 28 | id bigserial primary key, 29 | key_id bigint not null default 1, 30 | nonce bytea, 31 | encrypted_json bytea 32 | ); 33 | 34 | CREATE OR REPLACE FUNCTION encrypted_record_check() RETURNS trigger 35 | SECURITY DEFINER 36 | LANGUAGE plpgsql AS 37 | $$ 38 | BEGIN 39 | PERFORM pgsodium.crypto_secretbox_open( 40 | new.encrypted_json, 41 | new.nonce, 42 | new.key_id); 43 | RETURN new; 44 | END; 45 | $$; 46 | 47 | ALTER FUNCTION encrypted_record_check OWNER TO crypter; 48 | 49 | CREATE TRIGGER encrypted_record_check_trigger 50 | BEFORE INSERT ON encrypted_record 51 | FOR EACH ROW 52 | EXECUTE FUNCTION encrypted_record_check(); 53 | 54 | REVOKE ALL ON TABLE encrypted_record FROM PUBLIC; 55 | GRANT SELECT (id) ON TABLE encrypted_record TO staff; 56 | GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE encrypted_record TO crypter; 57 | GRANT USAGE ON SEQUENCE encrypted_record_id_seq TO crypter; 58 | 59 | CREATE OR REPLACE FUNCTION decrypt_record(bigint) 60 | RETURNS TABLE ( 61 | id bigint, 62 | first_name text, 63 | last_name text, 64 | age integer, 65 | secret text, 66 | signup_date timestamptz) 67 | LANGUAGE sql 68 | SECURITY DEFINER 69 | AS $$ 70 | SELECT e.id, rec.* 71 | FROM (SELECT * FROM encrypted_record WHERE id = $1) e 72 | LEFT JOIN LATERAL ( 73 | SELECT x.* FROM json_to_record( 74 | convert_from( 75 | pgsodium.crypto_secretbox_open( 76 | e.encrypted_json, 77 | e.nonce, 78 | e.key_id), 'utf8')::json) 79 | AS 80 | x(first_name text, 81 | last_name text, 82 | age int, 83 | secret text, 84 | signup_date timestamptz)) rec on true; 85 | $$; 86 | 87 | REVOKE ALL ON FUNCTION decrypt_record FROM PUBLIC; 88 | ALTER FUNCTION decrypt_record OWNER TO crypter; 89 | GRANT EXECUTE ON FUNCTION decrypt_record TO crypter; 90 | 91 | CREATE OR REPLACE FUNCTION pseudo_record(bigint) 92 | RETURNS TABLE ( 93 | id bigint, 94 | first_name text, 95 | last_name text, 96 | age_range int4range, 97 | secret text, 98 | signup_month timestamptz) 99 | LANGUAGE sql 100 | SECURITY DEFINER 101 | AS $$ 102 | SELECT e.id, 103 | anon.pseudo_first_name(rec.first_name) AS first_name, 104 | anon.pseudo_last_name(rec.last_name) AS last_name, 105 | anon.generalize_int4range(rec.age, 5) AS age, 106 | anon.partial(rec.secret, 2, 'xxxxxxxxx', 2) as hidden_secret, 107 | lower(anon.generalize_tstzrange(rec.signup_date, 'month')) AS secret_month 108 | FROM (SELECT * FROM encrypted_record WHERE id = $1) e 109 | LEFT JOIN LATERAL decrypt_record(e.id) rec on true; 110 | $$; 111 | 112 | ALTER FUNCTION pseudo_record OWNER TO crypter; 113 | 114 | CREATE OR REPLACE FUNCTION encrypt_record( 115 | first_name text, 116 | last_name text, 117 | age int, 118 | secret text, 119 | signup_date timestamptz) RETURNS bigint 120 | LANGUAGE plpgsql 121 | SECURITY DEFINER 122 | AS 123 | $$ 124 | DECLARE 125 | new_nonce bytea; 126 | encrypted_record_id bigint; 127 | payload jsonb; 128 | BEGIN 129 | new_nonce = pgsodium.crypto_secretbox_noncegen(); 130 | payload = json_build_object( 131 | 'first_name', first_name, 132 | 'last_name', last_name, 133 | 'age', age, 134 | 'secret', secret, 135 | 'signup_date', signup_date); 136 | 137 | INSERT INTO encrypted_record (nonce) VALUES (new_nonce) 138 | RETURNING id INTO encrypted_record_id; 139 | 140 | UPDATE encrypted_record SET 141 | encrypted_json = pgsodium.crypto_secretbox( 142 | convert_to(payload::text, 'utf8'), 143 | new_nonce, 144 | key_id) 145 | WHERE id = encrypted_record_id; 146 | RETURN encrypted_record_id; 147 | END; 148 | $$; 149 | 150 | ALTER FUNCTION encrypt_record OWNER TO crypter; 151 | 152 | 153 | CREATE OR REPLACE FUNCTION rotate_key(encrypted_record_id bigint, new_key bigint) 154 | RETURNS void 155 | SECURITY DEFINER 156 | LANGUAGE plpgsql AS $$ 157 | DECLARE 158 | new_nonce bytea; 159 | BEGIN 160 | new_nonce = pgsodium.crypto_secretbox_noncegen(); 161 | UPDATE encrypted_record SET 162 | nonce = new_nonce, 163 | key_id = new_key, 164 | encrypted_json = pgsodium.crypto_secretbox( 165 | pgsodium.crypto_secretbox_open( 166 | encrypted_record.encrypted_json, 167 | encrypted_record.nonce, 168 | encrypted_record.key_id), 169 | new_nonce, 170 | new_key) 171 | WHERE encrypted_record.id = encrypted_record_id; 172 | RETURN; 173 | END; 174 | $$; 175 | 176 | ALTER FUNCTION rotate_key OWNER TO crypter; 177 | 178 | CREATE OR REPLACE FUNCTION demo_data( 179 | num_records bigint, 180 | start_date timestamptz = '2010-01-01', 181 | end_date timestamptz = '2020-01-01') 182 | RETURNS void 183 | LANGUAGE sql 184 | SECURITY DEFINER 185 | AS $$ 186 | SELECT encrypt_record(anon.fake_first_name(), 187 | anon.fake_last_name(), 188 | anon.random_int_between(0, 110), 189 | anon.random_city(), 190 | anon.random_date_between(start_date, end_date)) 191 | FROM generate_series(1, num_records); 192 | $$; 193 | 194 | ALTER FUNCTION demo_data OWNER TO crypter; 195 | -------------------------------------------------------------------------------- /example/tce.sql: -------------------------------------------------------------------------------- 1 | \set ON_ERROR_ROLLBACK 0 2 | \set ON_ERROR_STOP on 3 | \set ECHO all 4 | 5 | CREATE EXTENSION IF NOT EXISTS pgsodium; 6 | 7 | DROP SCHEMA IF EXISTS "tce-example" CASCADE; 8 | CREATE SCHEMA "tce-example"; 9 | 10 | SET search_path = "tce-example", pg_catalog; 11 | 12 | CREATE TABLE test ( 13 | secret text, 14 | name text unique 15 | ); 16 | 17 | CREATE TABLE test2 ( 18 | id bigserial primary key, 19 | secret bytea, 20 | associated text, 21 | nonce bytea, 22 | secret2 text, 23 | associated2 text, 24 | secret2_key_id uuid, 25 | nonce2 bytea 26 | ); 27 | 28 | CREATE ROLE bob with login password 'foo'; 29 | GRANT INSERT ON "tce-example".test, "tce-example".test2 to bob; 30 | GRANT USAGE ON SEQUENCE test2_id_seq to bob; 31 | 32 | SECURITY LABEL FOR pgsodium ON ROLE bob is 'ACCESS "tce-example".test, "tce-example".test2'; 33 | 34 | SELECT format('ENCRYPT WITH KEY ID %s', (pgsodium.create_key('aead-det')).id) 35 | AS seclabel \gset 36 | 37 | SELECT format('ENCRYPT WITH KEY ID %s ASSOCIATED (associated) NONCE nonce', ( 38 | pgsodium.create_key('aead-det')).id) AS seclabel2 \gset 39 | 40 | SELECT id AS secret2_key_id FROM pgsodium.create_key('aead-det', 'foo_key') \gset 41 | 42 | SECURITY LABEL FOR pgsodium ON COLUMN test.secret IS :'seclabel'; 43 | 44 | SECURITY LABEL FOR pgsodium ON TABLE "tce-example".test2 IS 45 | 'DECRYPT WITH VIEW "tce-example"."other-test2"'; 46 | 47 | SECURITY LABEL FOR pgsodium ON COLUMN test2.secret IS :'seclabel2'; 48 | 49 | SECURITY LABEL FOR pgsodium ON COLUMN "tce-example".test2.secret2 IS 50 | 'ENCRYPT WITH KEY COLUMN secret2_key_id ASSOCIATED (id, associated2) NONCE nonce2'; 51 | 52 | SELECT pgsodium.crypto_aead_det_noncegen() aead_nonce \gset 53 | SELECT pgsodium.crypto_aead_det_noncegen() aead_nonce2 \gset 54 | 55 | GRANT ALL ON SCHEMA "tce-example" TO bob; 56 | select pgsodium.update_masks(true); 57 | 58 | COMMIT; 59 | \c postgres bob 60 | \x 61 | 62 | SET search_path = "tce-example", pg_catalog; 63 | 64 | INSERT INTO "tce-example".decrypted_test (secret) VALUES ('noice') RETURNING *; 65 | 66 | INSERT INTO "tce-example"."other-test2" (secret, associated, nonce, secret2, associated2, nonce2, secret2_key_id) 67 | VALUES ('sssh', 'bob was here', :'aead_nonce', 'aaahh', 'alice association', :'aead_nonce2', :'secret2_key_id'::uuid) RETURNING *; 68 | 69 | CREATE TABLE "tce-example"."bob-testt" ( 70 | "secret2-test" text, 71 | "associated2-test" text, 72 | "secret2_key_id-test" uuid DEFAULT (pgsodium.create_key()).id, 73 | "nonce2-test" bytea DEFAULT pgsodium.crypto_aead_det_noncegen() 74 | ); 75 | 76 | SECURITY LABEL FOR pgsodium ON COLUMN "bob-testt"."secret2-test" IS 77 | 'ENCRYPT WITH KEY COLUMN secret2_key_id-test ASSOCIATED (associated2-test) NONCE nonce2-test SECURITY INVOKER'; 78 | 79 | select pgsodium.update_masks(true); 80 | -------------------------------------------------------------------------------- /extension/pgsodium--1.0.0.sql: -------------------------------------------------------------------------------- 1 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 2 | \echo Use "CREATE EXTENSION pgsodium" to load this file. \quit 3 | 4 | CREATE FUNCTION randombytes_random() 5 | RETURNS integer 6 | AS '$libdir/pgsodium', 'pgsodium_randombytes_random' 7 | LANGUAGE C VOLATILE; 8 | 9 | CREATE FUNCTION randombytes_uniform(upper_bound integer) 10 | RETURNS integer 11 | AS '$libdir/pgsodium', 'pgsodium_randombytes_uniform' 12 | LANGUAGE C VOLATILE STRICT; 13 | 14 | CREATE FUNCTION randombytes_buf(size integer) 15 | RETURNS bytea 16 | AS '$libdir/pgsodium', 'pgsodium_randombytes_buf' 17 | LANGUAGE C VOLATILE STRICT; 18 | 19 | CREATE FUNCTION crypto_secretbox_keygen() 20 | RETURNS bytea 21 | AS '$libdir/pgsodium', 'pgsodium_crypto_secretbox_keygen' 22 | LANGUAGE C VOLATILE; 23 | 24 | CREATE FUNCTION crypto_secretbox_noncegen() 25 | RETURNS bytea 26 | AS '$libdir/pgsodium', 'pgsodium_crypto_secretbox_noncegen' 27 | LANGUAGE C VOLATILE; 28 | 29 | CREATE FUNCTION crypto_secretbox(message bytea, nonce bytea, key bytea) 30 | RETURNS bytea 31 | AS '$libdir/pgsodium', 'pgsodium_crypto_secretbox' 32 | LANGUAGE C IMMUTABLE STRICT; 33 | 34 | CREATE FUNCTION crypto_secretbox_open(ciphertext bytea, nonce bytea, key bytea) 35 | RETURNS bytea 36 | AS '$libdir/pgsodium', 'pgsodium_crypto_secretbox_open' 37 | LANGUAGE C IMMUTABLE STRICT; 38 | 39 | CREATE FUNCTION crypto_auth(message bytea, key bytea) 40 | RETURNS bytea 41 | AS '$libdir/pgsodium', 'pgsodium_crypto_auth' 42 | LANGUAGE C IMMUTABLE STRICT; 43 | 44 | CREATE FUNCTION crypto_auth_verify(mac bytea, message bytea, key bytea) 45 | RETURNS boolean 46 | AS '$libdir/pgsodium', 'pgsodium_crypto_auth_verify' 47 | LANGUAGE C IMMUTABLE STRICT; 48 | 49 | CREATE FUNCTION crypto_auth_keygen() 50 | RETURNS bytea 51 | AS '$libdir/pgsodium', 'pgsodium_crypto_auth_keygen' 52 | LANGUAGE C VOLATILE; 53 | 54 | CREATE FUNCTION crypto_generichash(message bytea, key bytea DEFAULT NULL) 55 | RETURNS bytea 56 | AS '$libdir/pgsodium', 'pgsodium_crypto_generichash' 57 | LANGUAGE C IMMUTABLE; 58 | 59 | CREATE FUNCTION crypto_shorthash(message bytea, key bytea) 60 | RETURNS bytea 61 | AS '$libdir/pgsodium', 'pgsodium_crypto_shorthash' 62 | LANGUAGE C IMMUTABLE STRICT; 63 | 64 | CREATE TYPE crypto_box_keypair AS (public bytea, secret bytea); 65 | 66 | CREATE OR REPLACE FUNCTION crypto_box_new_keypair() 67 | RETURNS SETOF crypto_box_keypair 68 | AS '$libdir/pgsodium', 'pgsodium_crypto_box_keypair' 69 | LANGUAGE C VOLATILE; 70 | 71 | CREATE FUNCTION crypto_box_noncegen() 72 | RETURNS bytea 73 | AS '$libdir/pgsodium', 'pgsodium_crypto_box_noncegen' 74 | LANGUAGE C VOLATILE; 75 | 76 | CREATE FUNCTION crypto_box(message bytea, nonce bytea, public bytea, secret bytea) 77 | RETURNS bytea 78 | AS '$libdir/pgsodium', 'pgsodium_crypto_box' 79 | LANGUAGE C IMMUTABLE STRICT; 80 | 81 | CREATE FUNCTION crypto_box_open(ciphertext bytea, nonce bytea, public bytea, secret bytea) 82 | RETURNS bytea 83 | AS '$libdir/pgsodium', 'pgsodium_crypto_box_open' 84 | LANGUAGE C IMMUTABLE STRICT; 85 | 86 | CREATE TYPE crypto_sign_keypair AS (public bytea, secret bytea); 87 | 88 | CREATE OR REPLACE FUNCTION crypto_sign_new_keypair() 89 | RETURNS SETOF crypto_sign_keypair 90 | AS '$libdir/pgsodium', 'pgsodium_crypto_sign_keypair' 91 | LANGUAGE C VOLATILE; 92 | 93 | CREATE OR REPLACE FUNCTION crypto_sign(message bytea, key bytea) 94 | RETURNS bytea 95 | AS '$libdir/pgsodium', 'pgsodium_crypto_sign' 96 | LANGUAGE C IMMUTABLE STRICT; 97 | 98 | CREATE OR REPLACE FUNCTION crypto_sign_open(signed_message bytea, key bytea) 99 | RETURNS bytea 100 | AS '$libdir/pgsodium', 'pgsodium_crypto_sign_open' 101 | LANGUAGE C IMMUTABLE STRICT; 102 | 103 | CREATE OR REPLACE FUNCTION crypto_sign_detached(message bytea, key bytea) 104 | RETURNS bytea 105 | AS '$libdir/pgsodium', 'pgsodium_crypto_sign_detached' 106 | LANGUAGE C IMMUTABLE STRICT; 107 | 108 | CREATE OR REPLACE FUNCTION crypto_sign_verify_detached(sig bytea, message bytea, key bytea) 109 | RETURNS boolean 110 | AS '$libdir/pgsodium', 'pgsodium_crypto_sign_verify_detached' 111 | LANGUAGE C IMMUTABLE STRICT; 112 | 113 | CREATE OR REPLACE FUNCTION crypto_pwhash_saltgen() 114 | RETURNS bytea 115 | AS '$libdir/pgsodium', 'pgsodium_crypto_pwhash_saltgen' 116 | LANGUAGE C VOLATILE; 117 | 118 | CREATE OR REPLACE FUNCTION crypto_pwhash(password bytea, salt bytea) 119 | RETURNS bytea 120 | AS '$libdir/pgsodium', 'pgsodium_crypto_pwhash' 121 | LANGUAGE C IMMUTABLE STRICT; 122 | 123 | CREATE OR REPLACE FUNCTION crypto_pwhash_str(password bytea) 124 | RETURNS bytea 125 | AS '$libdir/pgsodium', 'pgsodium_crypto_pwhash_str' 126 | LANGUAGE C VOLATILE STRICT; 127 | 128 | CREATE OR REPLACE FUNCTION crypto_pwhash_str_verify(hashed_password bytea, password bytea) 129 | RETURNS bool 130 | AS '$libdir/pgsodium', 'pgsodium_crypto_pwhash_str_verify' 131 | LANGUAGE C IMMUTABLE STRICT; 132 | 133 | CREATE OR REPLACE FUNCTION crypto_box_seal(message bytea, public_key bytea) 134 | RETURNS bytea 135 | AS '$libdir/pgsodium', 'pgsodium_crypto_box_seal' 136 | LANGUAGE C IMMUTABLE STRICT; 137 | 138 | CREATE OR REPLACE FUNCTION crypto_box_seal_open(ciphertext bytea, public_key bytea, secret_key bytea) 139 | RETURNS bytea 140 | AS '$libdir/pgsodium', 'pgsodium_crypto_box_seal_open' 141 | LANGUAGE C IMMUTABLE STRICT; 142 | -------------------------------------------------------------------------------- /extension/pgsodium--1.1.0--1.1.1.sql: -------------------------------------------------------------------------------- 1 | -- this is just a placeholder version to fix a bad release 2 | -------------------------------------------------------------------------------- /extension/pgsodium--2.0.0--2.0.1.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michelp/pgsodium/edc1e17b7a051a46e579c98fa7b16299ec31cdaf/extension/pgsodium--2.0.0--2.0.1.sql -------------------------------------------------------------------------------- /extension/pgsodium--2.0.1--2.0.2.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michelp/pgsodium/edc1e17b7a051a46e579c98fa7b16299ec31cdaf/extension/pgsodium--2.0.1--2.0.2.sql -------------------------------------------------------------------------------- /extension/pgsodium--3.0.0--3.0.2.sql: -------------------------------------------------------------------------------- 1 | 2 | ALTER FUNCTION pgsodium.crypto_aead_det_encrypt(message bytea, additional bytea, key_uuid uuid, nonce bytea) CALLED ON NULL INPUT; 3 | ALTER FUNCTION pgsodium.crypto_aead_det_decrypt(message bytea, additional bytea, key_uuid uuid, nonce bytea) CALLED ON NULL INPUT; 4 | -------------------------------------------------------------------------------- /extension/pgsodium--3.0.2--3.0.3.sql: -------------------------------------------------------------------------------- 1 | 2 | INSERT INTO pgsodium.key (status, key_id, key_context, comment) 3 | VALUES ('default', 1, 'pgsodium', 'This is the default key used for vault.secrets'); 4 | -------------------------------------------------------------------------------- /extension/pgsodium--3.0.3--3.0.4.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION pgsodium.encrypted_columns( 2 | relid OID 3 | ) 4 | RETURNS TEXT AS 5 | $$ 6 | DECLARE 7 | m RECORD; 8 | expression TEXT; 9 | comma TEXT; 10 | BEGIN 11 | expression := ''; 12 | comma := E' '; 13 | FOR m IN SELECT * FROM pgsodium.mask_columns(relid) LOOP 14 | IF m.key_id IS NULL AND m.key_id_column is NULL THEN 15 | CONTINUE; 16 | ELSE 17 | expression := expression || comma; 18 | expression := expression || format( 19 | $f$%s = pg_catalog.encode( 20 | pgsodium.crypto_aead_det_encrypt( 21 | pg_catalog.convert_to(%s, 'utf8'), 22 | pg_catalog.convert_to(%s::text, 'utf8'), 23 | %s::uuid, 24 | %s 25 | ), 26 | 'base64')$f$, 27 | 'new.' || quote_ident(m.attname), 28 | 'new.' || quote_ident(m.attname), 29 | COALESCE('new.' || quote_ident(m.associated_column), quote_literal('')), 30 | COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), 31 | COALESCE('new.' || quote_ident(m.nonce_column), 'NULL') 32 | ); 33 | END IF; 34 | comma := E';\n '; 35 | END LOOP; 36 | RETURN expression; 37 | END 38 | $$ 39 | LANGUAGE plpgsql 40 | VOLATILE 41 | SET search_path='' 42 | ; 43 | 44 | CREATE OR REPLACE FUNCTION pgsodium.decrypted_columns( 45 | relid OID 46 | ) 47 | RETURNS TEXT AS 48 | $$ 49 | DECLARE 50 | m RECORD; 51 | expression TEXT; 52 | comma TEXT; 53 | padding text = ' '; 54 | BEGIN 55 | expression := E'\n'; 56 | comma := padding; 57 | FOR m IN SELECT * FROM pgsodium.mask_columns(relid) LOOP 58 | expression := expression || comma; 59 | IF m.key_id IS NULL AND m.key_id_column IS NULL THEN 60 | expression := expression || padding || quote_ident(m.attname); 61 | ELSE 62 | expression := expression || padding || quote_ident(m.attname) || E',\n'; 63 | expression := expression || format( 64 | $f$ 65 | pg_catalog.convert_from( 66 | pgsodium.crypto_aead_det_decrypt( 67 | pg_catalog.decode(%s, 'base64'), 68 | pg_catalog.convert_to(%s::text, 'utf8'), 69 | %s::uuid, 70 | %s 71 | ), 72 | 'utf8') AS %s$f$, 73 | quote_ident(m.attname), 74 | coalesce(quote_ident(m.associated_column), quote_literal('')), 75 | coalesce(quote_ident(m.key_id_column), quote_literal(m.key_id)), 76 | coalesce(quote_ident(m.nonce_column), 'NULL'), 77 | 'decrypted_' || quote_ident(m.attname) 78 | ); 79 | END IF; 80 | comma := E', \n'; 81 | END LOOP; 82 | RETURN expression; 83 | END 84 | $$ 85 | LANGUAGE plpgsql 86 | VOLATILE 87 | SET search_path='' 88 | ; 89 | -------------------------------------------------------------------------------- /extension/pgsodium--3.0.5--3.0.6.sql: -------------------------------------------------------------------------------- 1 | DROP EVENT TRIGGER pgsodium_trg_mask_update; 2 | 3 | CREATE EVENT TRIGGER pgsodium_trg_mask_update 4 | ON ddl_command_end 5 | WHEN TAG IN ( 6 | 'SECURITY LABEL' 7 | ) 8 | EXECUTE PROCEDURE pgsodium.trg_mask_update() 9 | ; 10 | 11 | ALTER EXTENSION pgsodium DROP FUNCTION pgsodium.key_encrypt_secret(); 12 | 13 | CREATE FUNCTION pgsodium.update_mask(target oid, debug boolean = false) 14 | RETURNS void AS 15 | $$ 16 | BEGIN 17 | ALTER EVENT TRIGGER pgsodium_trg_mask_update DISABLE; 18 | PERFORM pgsodium.create_mask_view(objoid, objsubid, debug) 19 | FROM pg_catalog.pg_seclabel 20 | WHERE objoid = target 21 | AND label ILIKE 'ENCRYPT%' 22 | AND provider = 'pgsodium'; 23 | ALTER EVENT TRIGGER pgsodium_trg_mask_update ENABLE; 24 | RETURN; 25 | END 26 | $$ 27 | LANGUAGE plpgsql 28 | SECURITY DEFINER 29 | SET search_path='' 30 | ; 31 | 32 | CREATE OR REPLACE FUNCTION pgsodium.update_masks(debug boolean = false) 33 | RETURNS void AS 34 | $$ 35 | BEGIN 36 | PERFORM pgsodium.update_mask(objoid, debug) 37 | FROM pg_catalog.pg_seclabel 38 | WHERE label ilike 'ENCRYPT%' 39 | AND provider = 'pgsodium'; 40 | RETURN; 41 | END 42 | $$ 43 | LANGUAGE plpgsql 44 | SET search_path='' 45 | ; 46 | 47 | CREATE OR REPLACE FUNCTION pgsodium.create_mask_view(relid oid, subid integer, debug boolean = false) RETURNS void AS 48 | $$ 49 | DECLARE 50 | body text; 51 | source_name text; 52 | rule pgsodium.masking_rule; 53 | BEGIN 54 | SELECT * INTO STRICT rule FROM pgsodium.masking_rule WHERE attrelid = relid and attnum = subid ; 55 | 56 | source_name := relid::regclass; 57 | 58 | body = format( 59 | $c$ 60 | DROP VIEW IF EXISTS %s; 61 | CREATE VIEW %s AS SELECT %s 62 | FROM %s; 63 | $c$, 64 | rule.view_name, 65 | rule.view_name, 66 | pgsodium.decrypted_columns(relid), 67 | source_name 68 | ); 69 | IF debug THEN 70 | RAISE NOTICE '%', body; 71 | END IF; 72 | EXECUTE body; 73 | 74 | body = format( 75 | $c$ 76 | DROP FUNCTION IF EXISTS %s.%s_encrypt_secret() CASCADE; 77 | 78 | CREATE OR REPLACE FUNCTION %s.%s_encrypt_secret() 79 | RETURNS TRIGGER 80 | LANGUAGE plpgsql 81 | AS $t$ 82 | BEGIN 83 | %s; 84 | RETURN new; 85 | END; 86 | $t$; 87 | 88 | DROP TRIGGER IF EXISTS %s_encrypt_secret_trigger ON %s.%s; 89 | 90 | CREATE TRIGGER %s_encrypt_secret_trigger 91 | BEFORE INSERT ON %s 92 | FOR EACH ROW 93 | EXECUTE FUNCTION %s.%s_encrypt_secret (); 94 | $c$, 95 | rule.relnamespace, 96 | rule.relname, 97 | rule.relnamespace, 98 | rule.relname, 99 | pgsodium.encrypted_columns(relid), 100 | rule.relname, 101 | rule.relnamespace, 102 | rule.relname, 103 | rule.relname, 104 | source_name, 105 | rule.relnamespace, 106 | rule.relname 107 | ); 108 | if debug THEN 109 | RAISE NOTICE '%', body; 110 | END IF; 111 | EXECUTE body; 112 | 113 | PERFORM pgsodium.mask_role(oid::regrole, source_name, rule.view_name) 114 | FROM pg_roles WHERE pgsodium.has_mask(oid::regrole, source_name); 115 | 116 | RETURN; 117 | END 118 | $$ 119 | LANGUAGE plpgsql 120 | VOLATILE 121 | SET search_path='pg_catalog' 122 | ; 123 | -------------------------------------------------------------------------------- /extension/pgsodium--3.0.6--3.0.7.sql: -------------------------------------------------------------------------------- 1 | 2 | -- This is for bw compat with old dumps that don't go through the UPDATE TO process 3 | ALTER TABLE pgsodium.key ADD COLUMN comment text; 4 | 5 | CREATE FUNCTION pgsodium.get_key_by_id(uuid) RETURNS pgsodium.valid_key 6 | AS $$ 7 | SELECT * from pgsodium.valid_key WHERE id = $1; 8 | $$ 9 | SECURITY DEFINER 10 | LANGUAGE sql 11 | SET search_path = ''; 12 | 13 | 14 | CREATE FUNCTION pgsodium.get_key_by_name(text) RETURNS pgsodium.valid_key 15 | AS $$ 16 | SELECT * from pgsodium.valid_key WHERE name = $1; 17 | $$ 18 | SECURITY DEFINER 19 | LANGUAGE sql 20 | SET search_path = ''; 21 | 22 | CREATE FUNCTION pgsodium.get_named_keys(filter text='%') RETURNS SETOF pgsodium.valid_key 23 | AS $$ 24 | SELECT * from pgsodium.valid_key vk WHERE vk.name ILIKE filter; 25 | $$ 26 | SECURITY DEFINER 27 | LANGUAGE sql 28 | SET search_path = ''; 29 | 30 | 31 | DROP FUNCTION pgsodium.create_mask_view(oid, integer, boolean); 32 | CREATE FUNCTION pgsodium.create_mask_view(relid oid, subid integer, debug boolean = false) 33 | RETURNS void AS 34 | $$ 35 | DECLARE 36 | body text; 37 | source_name text; 38 | view_owner text = session_user; 39 | rule pgsodium.masking_rule; 40 | BEGIN 41 | SELECT * INTO STRICT rule FROM pgsodium.masking_rule WHERE attrelid = relid and attnum = subid ; 42 | 43 | source_name := relid::regclass; 44 | 45 | body = format( 46 | $c$ 47 | DROP VIEW IF EXISTS %s; 48 | CREATE VIEW %s AS SELECT %s 49 | FROM %s; 50 | ALTER VIEW %s OWNER TO %s; 51 | $c$, 52 | rule.view_name, 53 | rule.view_name, 54 | pgsodium.decrypted_columns(relid), 55 | source_name, 56 | rule.view_name, 57 | view_owner 58 | ); 59 | IF debug THEN 60 | RAISE NOTICE '%', body; 61 | END IF; 62 | EXECUTE body; 63 | 64 | body = format( 65 | $c$ 66 | DROP FUNCTION IF EXISTS %s.%s_encrypt_secret() CASCADE; 67 | 68 | CREATE OR REPLACE FUNCTION %s.%s_encrypt_secret() 69 | RETURNS TRIGGER 70 | LANGUAGE plpgsql 71 | AS $t$ 72 | BEGIN 73 | %s; 74 | RETURN new; 75 | END; 76 | $t$; 77 | 78 | ALTER FUNCTION %s.%s_encrypt_secret() OWNER TO %s; 79 | 80 | DROP TRIGGER IF EXISTS %s_encrypt_secret_trigger ON %s.%s; 81 | 82 | CREATE TRIGGER %s_encrypt_secret_trigger 83 | BEFORE INSERT ON %s 84 | FOR EACH ROW 85 | EXECUTE FUNCTION %s.%s_encrypt_secret (); 86 | $c$, 87 | rule.relnamespace, 88 | rule.relname, 89 | rule.relnamespace, 90 | rule.relname, 91 | pgsodium.encrypted_columns(relid), 92 | rule.relnamespace, 93 | rule.relname, 94 | view_owner, 95 | rule.relname, 96 | rule.relnamespace, 97 | rule.relname, 98 | rule.relname, 99 | source_name, 100 | rule.relnamespace, 101 | rule.relname 102 | ); 103 | if debug THEN 104 | RAISE NOTICE '%', body; 105 | END IF; 106 | EXECUTE body; 107 | 108 | PERFORM pgsodium.mask_role(oid::regrole, source_name, rule.view_name) 109 | FROM pg_roles WHERE pgsodium.has_mask(oid::regrole, source_name); 110 | 111 | RETURN; 112 | END 113 | $$ 114 | LANGUAGE plpgsql 115 | VOLATILE 116 | SET search_path='pg_catalog' 117 | ; 118 | 119 | CREATE FUNCTION pgsodium.enable_security_label_trigger() RETURNS void AS 120 | $$ 121 | ALTER EVENT TRIGGER pgsodium_trg_mask_update ENABLE; 122 | $$ 123 | LANGUAGE sql 124 | SECURITY DEFINER 125 | SET search_path='' 126 | ; 127 | 128 | CREATE FUNCTION pgsodium.disable_security_label_trigger() RETURNS void AS 129 | $$ 130 | ALTER EVENT TRIGGER pgsodium_trg_mask_update DISABLE; 131 | $$ 132 | LANGUAGE sql 133 | SECURITY DEFINER 134 | SET search_path='' 135 | ; 136 | 137 | DROP FUNCTION pgsodium.update_mask(oid, boolean); 138 | CREATE FUNCTION pgsodium.update_mask(target oid, debug boolean = false) 139 | RETURNS void AS 140 | $$ 141 | BEGIN 142 | PERFORM pgsodium.disable_security_label_trigger(); 143 | PERFORM pgsodium.create_mask_view(objoid, objsubid, debug) 144 | FROM pg_catalog.pg_seclabel sl 145 | WHERE sl.objoid = target 146 | AND sl.label ILIKE 'ENCRYPT%' 147 | AND sl.provider = 'pgsodium'; 148 | PERFORM pgsodium.enable_security_label_trigger(); 149 | RETURN; 150 | END 151 | $$ 152 | LANGUAGE plpgsql 153 | SECURITY DEFINER 154 | SET search_path='' 155 | ; 156 | 157 | DROP FUNCTION pgsodium.update_masks(boolean); 158 | CREATE FUNCTION pgsodium.update_masks(debug boolean = false) 159 | RETURNS void AS 160 | $$ 161 | BEGIN 162 | PERFORM pgsodium.update_mask(objoid, debug) 163 | FROM pg_catalog.pg_seclabel sl 164 | JOIN pg_catalog.pg_class cl ON (cl.oid = sl.objoid) 165 | WHERE label ilike 'ENCRYPT%' 166 | AND cl.relowner = session_user::regrole::oid 167 | AND provider = 'pgsodium'; 168 | RETURN; 169 | END 170 | $$ 171 | LANGUAGE plpgsql 172 | SET search_path='' 173 | ; 174 | 175 | CREATE OR REPLACE FUNCTION pgsodium.crypto_aead_det_encrypt(message bytea, additional bytea, key_uuid uuid, nonce bytea) 176 | RETURNS bytea AS 177 | $$ 178 | DECLARE 179 | key pgsodium.decrypted_key; 180 | BEGIN 181 | SELECT * INTO STRICT key 182 | FROM pgsodium.decrypted_key v 183 | WHERE id = key_uuid AND key_type = 'aead-det'; 184 | 185 | IF key.decrypted_raw_key IS NOT NULL THEN 186 | RETURN pgsodium.crypto_aead_det_encrypt(message, additional, key.decrypted_raw_key, nonce); 187 | END IF; 188 | RETURN pgsodium.crypto_aead_det_encrypt(message, additional, key.key_id, key.key_context, nonce); 189 | END; 190 | $$ 191 | LANGUAGE plpgsql 192 | SECURITY DEFINER 193 | STABLE 194 | SET search_path='' 195 | ; 196 | 197 | CREATE OR REPLACE FUNCTION pgsodium.mask_role(masked_role regrole, source_name text, view_name text) 198 | RETURNS void AS 199 | $$ 200 | DECLARE 201 | mask_schema REGNAMESPACE = 'pgsodium_masks'; 202 | source_schema REGNAMESPACE = (regexp_split_to_array(source_name, '\.'))[1]; 203 | BEGIN 204 | EXECUTE format( 205 | 'GRANT SELECT ON pgsodium.key TO %s', 206 | masked_role); 207 | 208 | EXECUTE format( 209 | 'GRANT pgsodium_keyiduser TO %s', 210 | masked_role); 211 | 212 | EXECUTE format( 213 | 'GRANT ALL ON %s TO %s', 214 | view_name, 215 | masked_role); 216 | RETURN; 217 | END 218 | $$ 219 | LANGUAGE plpgsql 220 | SECURITY DEFINER 221 | SET search_path='pg_catalog' 222 | ; 223 | 224 | -------------------------------------------------------------------------------- /extension/pgsodium--3.1.0--3.1.1.sql: -------------------------------------------------------------------------------- 1 | 2 | CREATE OR REPLACE FUNCTION pgsodium.encrypted_column(relid OID, m record) 3 | RETURNS TEXT AS 4 | $$ 5 | DECLARE 6 | expression TEXT; 7 | comma TEXT; 8 | BEGIN 9 | expression := ''; 10 | comma := E' '; 11 | expression := expression || comma; 12 | IF m.format_type = 'text' THEN 13 | expression := expression || format( 14 | $f$%s = CASE WHEN %s IS NULL THEN NULL ELSE 15 | CASE WHEN %s IS NULL THEN NULL ELSE pg_catalog.encode( 16 | pgsodium.crypto_aead_det_encrypt( 17 | pg_catalog.convert_to(%s, 'utf8'), 18 | pg_catalog.convert_to((%s)::text, 'utf8'), 19 | %s::uuid, 20 | %s 21 | ), 22 | 'base64') END END$f$, 23 | 'new.' || quote_ident(m.attname), 24 | 'new.' || quote_ident(m.attname), 25 | COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), 26 | 'new.' || quote_ident(m.attname), 27 | COALESCE(pgsodium.quote_assoc(m.associated_columns, true), quote_literal('')), 28 | COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), 29 | COALESCE('new.' || quote_ident(m.nonce_column), 'NULL') 30 | ); 31 | ELSIF m.format_type = 'bytea' THEN 32 | expression := expression || format( 33 | $f$%s = CASE WHEN %s IS NULL THEN NULL ELSE 34 | CASE WHEN %s IS NULL THEN NULL ELSE 35 | pgsodium.crypto_aead_det_encrypt(%s::bytea, pg_catalog.convert_to((%s)::text, 'utf8'), 36 | %s::uuid, 37 | %s 38 | ) END END$f$, 39 | 'new.' || quote_ident(m.attname), 40 | 'new.' || quote_ident(m.attname), 41 | COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), 42 | 'new.' || quote_ident(m.attname), 43 | COALESCE(pgsodium.quote_assoc(m.associated_columns, true), quote_literal('')), 44 | COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), 45 | COALESCE('new.' || quote_ident(m.nonce_column), 'NULL') 46 | ); 47 | END IF; 48 | comma := E';\n '; 49 | RETURN expression; 50 | END 51 | $$ 52 | LANGUAGE plpgsql 53 | VOLATILE 54 | SET search_path='' 55 | ; 56 | 57 | 58 | CREATE OR REPLACE FUNCTION pgsodium.create_mask_view(relid oid, subid integer, debug boolean = false) 59 | RETURNS void AS 60 | $$ 61 | DECLARE 62 | m record; 63 | body text; 64 | source_name text; 65 | view_owner regrole = session_user; 66 | rule pgsodium.masking_rule; 67 | BEGIN 68 | SELECT * INTO STRICT rule FROM pgsodium.masking_rule WHERE attrelid = relid and attnum = subid ; 69 | 70 | source_name := relid::regclass; 71 | 72 | body = format( 73 | $c$ 74 | DROP VIEW IF EXISTS %s; 75 | CREATE VIEW %s AS SELECT %s 76 | FROM %s; 77 | ALTER VIEW %s OWNER TO %s; 78 | $c$, 79 | rule.view_name, 80 | rule.view_name, 81 | pgsodium.decrypted_columns(relid), 82 | source_name, 83 | rule.view_name, 84 | view_owner 85 | ); 86 | IF debug THEN 87 | RAISE NOTICE '%', body; 88 | END IF; 89 | EXECUTE body; 90 | 91 | FOR m IN SELECT * FROM pgsodium.mask_columns where attrelid = relid LOOP 92 | IF m.key_id IS NULL AND m.key_id_column is NULL THEN 93 | CONTINUE; 94 | ELSE 95 | body = format( 96 | $c$ 97 | DROP FUNCTION IF EXISTS %s."%s_encrypt_secret_%s"() CASCADE; 98 | 99 | CREATE OR REPLACE FUNCTION %s."%s_encrypt_secret_%s"() 100 | RETURNS TRIGGER 101 | LANGUAGE plpgsql 102 | AS $t$ 103 | BEGIN 104 | %s; 105 | RETURN new; 106 | END; 107 | $t$; 108 | 109 | ALTER FUNCTION %s."%s_encrypt_secret_%s"() OWNER TO %s; 110 | 111 | DROP TRIGGER IF EXISTS "%s_encrypt_secret_trigger_%s" ON %s; 112 | 113 | CREATE TRIGGER "%s_encrypt_secret_trigger_%s" 114 | BEFORE INSERT OR UPDATE OF "%s" ON %s 115 | FOR EACH ROW 116 | EXECUTE FUNCTION %s."%s_encrypt_secret_%s" (); 117 | $c$, 118 | rule.relnamespace, 119 | rule.relname, 120 | m.attname, 121 | rule.relnamespace, 122 | rule.relname, 123 | m.attname, 124 | pgsodium.encrypted_column(relid, m), 125 | rule.relnamespace, 126 | rule.relname, 127 | m.attname, 128 | view_owner, 129 | rule.relname, 130 | m.attname, 131 | source_name, 132 | rule.relname, 133 | m.attname, 134 | m.attname, 135 | source_name, 136 | rule.relnamespace, 137 | rule.relname, 138 | m.attname 139 | ); 140 | if debug THEN 141 | RAISE NOTICE '%', body; 142 | END IF; 143 | EXECUTE body; 144 | END IF; 145 | END LOOP; 146 | 147 | PERFORM pgsodium.mask_role(oid::regrole, source_name, rule.view_name) 148 | FROM pg_roles WHERE pgsodium.has_mask(oid::regrole, source_name); 149 | 150 | RETURN; 151 | END 152 | $$ 153 | LANGUAGE plpgsql 154 | VOLATILE 155 | SET search_path='pg_catalog' 156 | ; 157 | 158 | CREATE OR REPLACE FUNCTION pgsodium.update_masks(debug boolean = false) 159 | RETURNS void AS 160 | $$ 161 | BEGIN 162 | PERFORM pgsodium.update_mask(objoid, debug) 163 | FROM pg_catalog.pg_seclabel sl 164 | JOIN pg_catalog.pg_class cl ON (cl.oid = sl.objoid) 165 | WHERE label ilike 'ENCRYPT%' 166 | AND cl.relowner = session_user::regrole::oid 167 | AND provider = 'pgsodium' 168 | AND objoid::regclass != 'pgsodium.key'::regclass 169 | ; 170 | RETURN; 171 | END 172 | $$ 173 | LANGUAGE plpgsql 174 | SET search_path='' 175 | ; 176 | 177 | DROP TRIGGER key_encrypt_secret_trigger ON pgsodium.key; 178 | DROP FUNCTION pgsodium.key_encrypt_secret(); 179 | SELECT pgsodium.update_mask('pgsodium.key'::regclass::oid); 180 | -------------------------------------------------------------------------------- /extension/pgsodium--3.1.1--3.1.2.sql: -------------------------------------------------------------------------------- 1 | DROP EVENT TRIGGER pgsodium_trg_mask_update; 2 | 3 | CREATE EVENT TRIGGER pgsodium_trg_mask_update 4 | ON ddl_command_end 5 | WHEN TAG IN ( 6 | 'SECURITY LABEL', 7 | 'ALTER TABLE' 8 | ) 9 | EXECUTE PROCEDURE pgsodium.trg_mask_update() 10 | ; 11 | -------------------------------------------------------------------------------- /extension/pgsodium--3.1.2--3.1.3.sql: -------------------------------------------------------------------------------- 1 | 2 | CREATE OR REPLACE FUNCTION pgsodium.trg_mask_update() 3 | RETURNS EVENT_TRIGGER AS 4 | $$ 5 | DECLARE 6 | r record; 7 | BEGIN 8 | IF (select bool_or(in_extension) FROM pg_event_trigger_ddl_commands()) THEN 9 | RAISE NOTICE 'skipping pgsodium mask regeneration in extension'; 10 | RETURN; 11 | END IF; 12 | PERFORM @extschema@.update_masks(); 13 | END 14 | $$ 15 | LANGUAGE plpgsql 16 | SET search_path='' 17 | ; 18 | 19 | -------------------------------------------------------------------------------- /extension/pgsodium--3.1.3--3.1.4.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE pgsodium.key ADD COLUMN user_data text; -- deprecated for b/w compat with <= 3.0.4 2 | -------------------------------------------------------------------------------- /extension/pgsodium--3.1.4--3.1.5.sql: -------------------------------------------------------------------------------- 1 | 2 | 3 | CREATE OR REPLACE FUNCTION pgsodium.create_mask_view(relid oid, subid integer, debug boolean = false) 4 | RETURNS void AS 5 | $$ 6 | DECLARE 7 | m record; 8 | body text; 9 | source_name text; 10 | view_owner regrole = session_user; 11 | rule pgsodium.masking_rule; 12 | privs aclitem[]; 13 | priv record; 14 | BEGIN 15 | SELECT DISTINCT * INTO STRICT rule FROM pgsodium.masking_rule WHERE attrelid = relid AND attnum = subid; 16 | 17 | source_name := relid::regclass::text; 18 | 19 | BEGIN 20 | SELECT relacl INTO STRICT privs FROM pg_catalog.pg_class WHERE oid = rule.view_name::regclass::oid; 21 | EXCEPTION 22 | WHEN undefined_table THEN 23 | SELECT relacl INTO STRICT privs FROM pg_catalog.pg_class WHERE oid = relid; 24 | END; 25 | 26 | body = format( 27 | $c$ 28 | DROP VIEW IF EXISTS %s; 29 | CREATE VIEW %s AS SELECT %s 30 | FROM %s; 31 | ALTER VIEW %s OWNER TO %s; 32 | $c$, 33 | rule.view_name, 34 | rule.view_name, 35 | pgsodium.decrypted_columns(relid), 36 | source_name, 37 | rule.view_name, 38 | view_owner 39 | ); 40 | IF debug THEN 41 | RAISE NOTICE '%', body; 42 | END IF; 43 | EXECUTE body; 44 | 45 | FOR priv IN SELECT * FROM pg_catalog.aclexplode(privs) LOOP 46 | body = format( 47 | $c$ 48 | GRANT %s ON %s TO %s; 49 | $c$, 50 | priv.privilege_type, 51 | rule.view_name, 52 | priv.grantee::regrole::text 53 | ); 54 | IF debug THEN 55 | RAISE NOTICE '%', body; 56 | END IF; 57 | EXECUTE body; 58 | END LOOP; 59 | 60 | FOR m IN SELECT * FROM pgsodium.mask_columns where attrelid = relid LOOP 61 | IF m.key_id IS NULL AND m.key_id_column is NULL THEN 62 | CONTINUE; 63 | ELSE 64 | body = format( 65 | $c$ 66 | DROP FUNCTION IF EXISTS %s."%s_encrypt_secret_%s"() CASCADE; 67 | 68 | CREATE OR REPLACE FUNCTION %s."%s_encrypt_secret_%s"() 69 | RETURNS TRIGGER 70 | LANGUAGE plpgsql 71 | AS $t$ 72 | BEGIN 73 | %s; 74 | RETURN new; 75 | END; 76 | $t$; 77 | 78 | ALTER FUNCTION %s."%s_encrypt_secret_%s"() OWNER TO %s; 79 | 80 | DROP TRIGGER IF EXISTS "%s_encrypt_secret_trigger_%s" ON %s; 81 | 82 | CREATE TRIGGER "%s_encrypt_secret_trigger_%s" 83 | BEFORE INSERT OR UPDATE OF "%s" ON %s 84 | FOR EACH ROW 85 | EXECUTE FUNCTION %s."%s_encrypt_secret_%s" (); 86 | $c$, 87 | rule.relnamespace, 88 | rule.relname, 89 | m.attname, 90 | rule.relnamespace, 91 | rule.relname, 92 | m.attname, 93 | pgsodium.encrypted_column(relid, m), 94 | rule.relnamespace, 95 | rule.relname, 96 | m.attname, 97 | view_owner, 98 | rule.relname, 99 | m.attname, 100 | source_name, 101 | rule.relname, 102 | m.attname, 103 | m.attname, 104 | source_name, 105 | rule.relnamespace, 106 | rule.relname, 107 | m.attname 108 | ); 109 | if debug THEN 110 | RAISE NOTICE '%', body; 111 | END IF; 112 | EXECUTE body; 113 | END IF; 114 | END LOOP; 115 | 116 | PERFORM pgsodium.mask_role(oid::regrole, source_name, rule.view_name) 117 | FROM pg_roles WHERE pgsodium.has_mask(oid::regrole, source_name); 118 | 119 | RETURN; 120 | END 121 | $$ 122 | LANGUAGE plpgsql 123 | VOLATILE 124 | SET search_path='pg_catalog' 125 | ; 126 | -------------------------------------------------------------------------------- /extension/pgsodium--3.1.5--3.1.6.sql: -------------------------------------------------------------------------------- 1 | 2 | SELECT pg_catalog.pg_extension_config_dump('pgsodium.key_key_id_seq', ''); 3 | -------------------------------------------------------------------------------- /extension/pgsodium--3.1.6--3.1.7.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE VIEW pgsodium.masking_rule AS 2 | WITH const AS ( 3 | SELECT 4 | 'encrypt +with +key +id +([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})' 5 | AS pattern_key_id, 6 | 'encrypt +with +key +column +([\w\"\-$]+)' 7 | AS pattern_key_id_column, 8 | '(?<=associated) +\(([\w\"\-$, ]+)\)' 9 | AS pattern_associated_columns, 10 | '(?<=nonce) +([\w\"\-$]+)' 11 | AS pattern_nonce_column, 12 | '(?<=decrypt with view) +([\w\"\-$]+\.[\w\"\-$]+)' 13 | AS pattern_view_name, 14 | '(?<=security invoker)' 15 | AS pattern_security_invoker 16 | ), 17 | rules_from_seclabels AS ( 18 | SELECT 19 | sl.objoid AS attrelid, 20 | sl.objsubid AS attnum, 21 | c.relnamespace::regnamespace, 22 | c.relname, 23 | a.attname, 24 | pg_catalog.format_type(a.atttypid, a.atttypmod), 25 | sl.label AS col_description, 26 | (regexp_match(sl.label, k.pattern_key_id_column, 'i'))[1] AS key_id_column, 27 | (regexp_match(sl.label, k.pattern_key_id, 'i'))[1] AS key_id, 28 | (regexp_match(sl.label, k.pattern_associated_columns, 'i'))[1] AS associated_columns, 29 | (regexp_match(sl.label, k.pattern_nonce_column, 'i'))[1] AS nonce_column, 30 | coalesce((regexp_match(sl2.label, k.pattern_view_name, 'i'))[1], 31 | c.relnamespace::regnamespace || '.' || quote_ident('decrypted_' || c.relname)) AS view_name, 32 | 100 AS priority, 33 | (regexp_match(sl.label, k.pattern_security_invoker, 'i'))[1] IS NOT NULL AS security_invoker 34 | FROM const k, 35 | pg_catalog.pg_seclabel sl 36 | JOIN pg_catalog.pg_class c ON sl.classoid = c.tableoid AND sl.objoid = c.oid 37 | JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid AND sl.objsubid = a.attnum 38 | LEFT JOIN pg_catalog.pg_seclabel sl2 ON sl2.objoid = c.oid AND sl2.objsubid = 0 39 | WHERE a.attnum > 0 40 | AND c.relnamespace::regnamespace != 'pg_catalog'::regnamespace 41 | AND NOT a.attisdropped 42 | AND sl.label ilike 'ENCRYPT%' 43 | AND sl.provider = 'pgsodium' 44 | ) 45 | SELECT 46 | DISTINCT ON (attrelid, attnum) * 47 | FROM rules_from_seclabels 48 | ORDER BY attrelid, attnum, priority DESC; 49 | 50 | CREATE OR REPLACE FUNCTION pgsodium.mask_role(masked_role regrole, source_name text, view_name text) 51 | RETURNS void AS 52 | $$ 53 | BEGIN 54 | EXECUTE format( 55 | 'GRANT SELECT ON pgsodium.key TO %s', 56 | masked_role); 57 | 58 | EXECUTE format( 59 | 'GRANT pgsodium_keyiduser, pgsodium_keyholder TO %s', 60 | masked_role); 61 | 62 | EXECUTE format( 63 | 'GRANT ALL ON %s TO %s', 64 | view_name, 65 | masked_role); 66 | RETURN; 67 | END 68 | $$ 69 | LANGUAGE plpgsql 70 | SECURITY DEFINER 71 | SET search_path='pg_catalog' 72 | ; 73 | 74 | CREATE OR REPLACE FUNCTION pgsodium.create_mask_view(relid oid, subid integer, debug boolean = false) 75 | RETURNS void AS 76 | $$ 77 | DECLARE 78 | m record; 79 | body text; 80 | source_name text; 81 | view_owner regrole = session_user; 82 | rule pgsodium.masking_rule; 83 | privs aclitem[]; 84 | priv record; 85 | BEGIN 86 | SELECT DISTINCT * INTO STRICT rule FROM pgsodium.masking_rule WHERE attrelid = relid AND attnum = subid; 87 | 88 | source_name := relid::regclass::text; 89 | 90 | BEGIN 91 | SELECT relacl INTO STRICT privs FROM pg_catalog.pg_class WHERE oid = rule.view_name::regclass::oid; 92 | EXCEPTION 93 | WHEN undefined_table THEN 94 | SELECT relacl INTO STRICT privs FROM pg_catalog.pg_class WHERE oid = relid; 95 | END; 96 | 97 | body = format( 98 | $c$ 99 | DROP VIEW IF EXISTS %1$s; 100 | CREATE VIEW %1$s %5$s AS SELECT %2$s 101 | FROM %3$s; 102 | ALTER VIEW %1$s OWNER TO %4$s; 103 | $c$, 104 | rule.view_name, 105 | pgsodium.decrypted_columns(relid), 106 | source_name, 107 | view_owner, 108 | CASE WHEN rule.security_invoker THEN 'WITH (security_invoker=true)' ELSE '' END 109 | ); 110 | IF debug THEN 111 | RAISE NOTICE '%', body; 112 | END IF; 113 | EXECUTE body; 114 | 115 | FOR priv IN SELECT * FROM pg_catalog.aclexplode(privs) LOOP 116 | body = format( 117 | $c$ 118 | GRANT %s ON %s TO %s; 119 | $c$, 120 | priv.privilege_type, 121 | rule.view_name, 122 | priv.grantee::regrole::text 123 | ); 124 | IF debug THEN 125 | RAISE NOTICE '%', body; 126 | END IF; 127 | EXECUTE body; 128 | END LOOP; 129 | 130 | FOR m IN SELECT * FROM pgsodium.mask_columns where attrelid = relid LOOP 131 | IF m.key_id IS NULL AND m.key_id_column is NULL THEN 132 | CONTINUE; 133 | ELSE 134 | body = format( 135 | $c$ 136 | DROP FUNCTION IF EXISTS %1$s."%2$s_encrypt_secret_%3$s"() CASCADE; 137 | 138 | CREATE OR REPLACE FUNCTION %1$s."%2$s_encrypt_secret_%3$s"() 139 | RETURNS TRIGGER 140 | LANGUAGE plpgsql 141 | AS $t$ 142 | BEGIN 143 | %4$s; 144 | RETURN new; 145 | END; 146 | $t$; 147 | 148 | ALTER FUNCTION %1$s."%2$s_encrypt_secret_%3$s"() OWNER TO %5$s; 149 | 150 | DROP TRIGGER IF EXISTS "%2$s_encrypt_secret_trigger_%3$s" ON %6$s; 151 | 152 | CREATE TRIGGER "%2$s_encrypt_secret_trigger_%3$s" 153 | BEFORE INSERT OR UPDATE OF "%3$s" ON %6$s 154 | FOR EACH ROW 155 | EXECUTE FUNCTION %1$s."%2$s_encrypt_secret_%3$s" (); 156 | $c$, 157 | rule.relnamespace, 158 | rule.relname, 159 | m.attname, 160 | pgsodium.encrypted_column(relid, m), 161 | view_owner, 162 | source_name 163 | ); 164 | if debug THEN 165 | RAISE NOTICE '%', body; 166 | END IF; 167 | EXECUTE body; 168 | END IF; 169 | END LOOP; 170 | 171 | raise notice 'about to masking role % %', source_name, rule.view_name; 172 | PERFORM pgsodium.mask_role(oid::regrole, source_name, rule.view_name) 173 | FROM pg_roles WHERE pgsodium.has_mask(oid::regrole, source_name); 174 | 175 | RETURN; 176 | END 177 | $$ 178 | LANGUAGE plpgsql 179 | VOLATILE 180 | SET search_path='pg_catalog' 181 | ; 182 | -------------------------------------------------------------------------------- /extension/pgsodium--3.1.7--3.1.8.sql: -------------------------------------------------------------------------------- 1 | 2 | CREATE OR REPLACE FUNCTION pgsodium.trg_mask_update() 3 | RETURNS EVENT_TRIGGER AS 4 | $$ 5 | DECLARE 6 | r record; 7 | BEGIN 8 | IF (SELECT bool_or(in_extension) FROM pg_event_trigger_ddl_commands()) THEN 9 | RAISE NOTICE 'skipping pgsodium mask regeneration in extension'; 10 | RETURN; 11 | END IF; 12 | 13 | FOR r IN 14 | SELECT e.* 15 | FROM pg_event_trigger_ddl_commands() e 16 | WHERE EXISTS ( 17 | SELECT FROM pg_catalog.pg_class c 18 | JOIN pg_catalog.pg_seclabel s ON s.classoid = c.tableoid 19 | AND s.objoid = c.oid 20 | WHERE c.tableoid = e.classid 21 | AND e.objid = c.oid 22 | AND s.provider = 'pgsodium' 23 | ) 24 | LOOP 25 | IF r.object_type in ('table', 'table column') 26 | THEN 27 | PERFORM pgsodium.update_mask(r.objid); 28 | END IF; 29 | END LOOP; 30 | END 31 | $$ 32 | LANGUAGE plpgsql 33 | SET search_path='' 34 | ; 35 | -------------------------------------------------------------------------------- /extension/pgsodium--3.1.8--3.1.9.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION pgsodium.create_mask_view(relid oid, subid integer, debug boolean = false) 2 | RETURNS void AS 3 | $$ 4 | DECLARE 5 | m record; 6 | body text; 7 | source_name text; 8 | view_owner regrole = quote_ident(session_user); 9 | rule pgsodium.masking_rule; 10 | privs aclitem[]; 11 | priv record; 12 | BEGIN 13 | SELECT DISTINCT * INTO STRICT rule FROM pgsodium.masking_rule WHERE attrelid = relid AND attnum = subid; 14 | 15 | source_name := relid::regclass::text; 16 | 17 | BEGIN 18 | SELECT relacl INTO STRICT privs FROM pg_catalog.pg_class WHERE oid = rule.view_name::regclass::oid; 19 | EXCEPTION 20 | WHEN undefined_table THEN 21 | SELECT relacl INTO STRICT privs FROM pg_catalog.pg_class WHERE oid = relid; 22 | END; 23 | 24 | body = format( 25 | $c$ 26 | DROP VIEW IF EXISTS %1$s; 27 | CREATE VIEW %1$s %5$s AS SELECT %2$s 28 | FROM %3$s; 29 | ALTER VIEW %1$s OWNER TO %4$s; 30 | $c$, 31 | rule.view_name, 32 | pgsodium.decrypted_columns(relid), 33 | source_name, 34 | view_owner, 35 | CASE WHEN rule.security_invoker THEN 'WITH (security_invoker=true)' ELSE '' END 36 | ); 37 | IF debug THEN 38 | RAISE NOTICE '%', body; 39 | END IF; 40 | EXECUTE body; 41 | 42 | FOR priv IN SELECT * FROM pg_catalog.aclexplode(privs) LOOP 43 | body = format( 44 | $c$ 45 | GRANT %s ON %s TO %s; 46 | $c$, 47 | priv.privilege_type, 48 | rule.view_name, 49 | priv.grantee::regrole::text 50 | ); 51 | IF debug THEN 52 | RAISE NOTICE '%', body; 53 | END IF; 54 | EXECUTE body; 55 | END LOOP; 56 | 57 | FOR m IN SELECT * FROM pgsodium.mask_columns where attrelid = relid LOOP 58 | IF m.key_id IS NULL AND m.key_id_column is NULL THEN 59 | CONTINUE; 60 | ELSE 61 | body = format( 62 | $c$ 63 | DROP FUNCTION IF EXISTS %1$s."%2$s_encrypt_secret_%3$s"() CASCADE; 64 | 65 | CREATE OR REPLACE FUNCTION %1$s."%2$s_encrypt_secret_%3$s"() 66 | RETURNS TRIGGER 67 | LANGUAGE plpgsql 68 | AS $t$ 69 | BEGIN 70 | %4$s; 71 | RETURN new; 72 | END; 73 | $t$; 74 | 75 | ALTER FUNCTION %1$s."%2$s_encrypt_secret_%3$s"() OWNER TO %5$s; 76 | 77 | DROP TRIGGER IF EXISTS "%2$s_encrypt_secret_trigger_%3$s" ON %6$s; 78 | 79 | CREATE TRIGGER "%2$s_encrypt_secret_trigger_%3$s" 80 | BEFORE INSERT OR UPDATE OF "%3$s" ON %6$s 81 | FOR EACH ROW 82 | EXECUTE FUNCTION %1$s."%2$s_encrypt_secret_%3$s" (); 83 | $c$, 84 | rule.relnamespace, 85 | rule.relname, 86 | m.attname, 87 | pgsodium.encrypted_column(relid, m), 88 | view_owner, 89 | source_name 90 | ); 91 | if debug THEN 92 | RAISE NOTICE '%', body; 93 | END IF; 94 | EXECUTE body; 95 | END IF; 96 | END LOOP; 97 | 98 | RAISE NOTICE 'Masking role % %', source_name, rule.view_name; 99 | PERFORM pgsodium.mask_role(oid::regrole, source_name, rule.view_name) 100 | FROM pg_roles WHERE pgsodium.has_mask(oid::regrole, source_name); 101 | 102 | RETURN; 103 | END 104 | $$ 105 | LANGUAGE plpgsql 106 | VOLATILE 107 | SET search_path='pg_catalog' 108 | ; 109 | 110 | CREATE OR REPLACE FUNCTION pgsodium.update_masks(debug boolean = false) 111 | RETURNS void AS 112 | $$ 113 | BEGIN 114 | PERFORM pgsodium.update_mask(objoid, debug) 115 | FROM pg_catalog.pg_seclabel sl 116 | JOIN pg_catalog.pg_class cl ON (cl.oid = sl.objoid) 117 | WHERE label ilike 'ENCRYPT%' 118 | AND cl.relowner = quote_ident(session_user)::regrole::oid 119 | AND provider = 'pgsodium' 120 | AND objoid::regclass != 'pgsodium.key'::regclass 121 | ; 122 | RETURN; 123 | END 124 | $$ 125 | LANGUAGE plpgsql 126 | SET search_path='' 127 | ; 128 | 129 | CREATE OR REPLACE FUNCTION pgsodium.trg_mask_update() 130 | RETURNS EVENT_TRIGGER AS 131 | $$ 132 | DECLARE 133 | r record; 134 | BEGIN 135 | IF (SELECT bool_or(in_extension) FROM pg_event_trigger_ddl_commands()) THEN 136 | RAISE NOTICE 'skipping pgsodium mask regeneration in extension'; 137 | RETURN; 138 | ELSIF current_setting('pgsodium.enable_event_trigger') <> 'on' THEN 139 | RAISE NOTICE 'skipping pgsodium mask regeneration due to false pgsodium.enable_event_trigger'; 140 | RETURN; 141 | END IF; 142 | 143 | FOR r IN 144 | SELECT e.* 145 | FROM pg_event_trigger_ddl_commands() e 146 | WHERE EXISTS ( 147 | SELECT FROM pg_catalog.pg_class c 148 | JOIN pg_catalog.pg_seclabel s ON s.classoid = c.tableoid 149 | AND s.objoid = c.oid 150 | WHERE c.tableoid = e.classid 151 | AND e.objid = c.oid 152 | AND s.provider = 'pgsodium' 153 | ) 154 | LOOP 155 | IF r.object_type in ('table', 'table column') 156 | THEN 157 | PERFORM pgsodium.update_mask(r.objid); 158 | END IF; 159 | END LOOP; 160 | END 161 | $$ 162 | LANGUAGE plpgsql 163 | SET search_path='' 164 | ; 165 | 166 | CREATE OR REPLACE FUNCTION pgsodium.encrypted_column(relid OID, m record) 167 | RETURNS TEXT AS 168 | $$ 169 | DECLARE 170 | expression TEXT; 171 | BEGIN 172 | expression := ''; 173 | IF m.format_type = 'text' THEN 174 | expression := expression || format($f$ 175 | IF %1$s = '' THEN RAISE EXCEPTION 'Cannot encrypt empty string.'; END IF; 176 | %1$s = CASE WHEN %1$s IS NULL THEN NULL ELSE 177 | CASE WHEN %2$s IS NULL THEN NULL ELSE pg_catalog.encode( 178 | pgsodium.crypto_aead_det_encrypt( 179 | pg_catalog.convert_to(%1$s, 'utf8'), 180 | pg_catalog.convert_to((%3$s)::text, 'utf8'), 181 | %2$s::uuid, 182 | %4$s 183 | ), 184 | 'base64') END END$f$, 185 | 'new.' || quote_ident(m.attname), 186 | COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), 187 | COALESCE(pgsodium.quote_assoc(m.associated_columns, true), quote_literal('')), 188 | COALESCE('new.' || quote_ident(m.nonce_column), 'NULL') 189 | ); 190 | ELSIF m.format_type = 'bytea' THEN 191 | expression := expression || format($f$ 192 | IF %1$s = ''::bytea THEN RAISE EXCEPTION 'Cannot encrypt empty bytes.'; END IF; 193 | %1$s = CASE WHEN %1$s IS NULL THEN NULL ELSE 194 | CASE WHEN %2$s IS NULL THEN NULL ELSE 195 | pgsodium.crypto_aead_det_encrypt(%1$s::bytea, pg_catalog.convert_to((%3$s)::text, 'utf8'), 196 | %2$s::uuid, 197 | %4$s 198 | ) END END$f$, 199 | 'new.' || quote_ident(m.attname), 200 | COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), 201 | COALESCE(pgsodium.quote_assoc(m.associated_columns, true), quote_literal('')), 202 | COALESCE('new.' || quote_ident(m.nonce_column), 'NULL') 203 | ); 204 | END IF; 205 | RETURN expression; 206 | END 207 | $$ 208 | LANGUAGE plpgsql 209 | VOLATILE 210 | SET search_path='' 211 | ; 212 | 213 | 214 | CREATE VIEW pgsodium.seclabel AS 215 | SELECT nspname, relname, attname, label 216 | FROM pg_seclabel sl, 217 | pg_class c, 218 | pg_attribute a, 219 | pg_namespace n 220 | WHERE sl.objoid = c.oid 221 | AND c.oid = a.attrelid 222 | AND a.attnum = sl.objsubid 223 | AND n.oid = c.relnamespace; 224 | -------------------------------------------------------------------------------- /getkey_scripts/pgsodium_getkey.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set KEY_FILE="%PGDATA%/pgsodium_root.key" 3 | 4 | IF NOT EXIST %KEY_FILE% ( 5 | openssl rand -hex 32 > %KEY_FILE% 6 | ) 7 | type "%PGDATA%/pgsodium_root.key" -------------------------------------------------------------------------------- /getkey_scripts/pgsodium_getkey_aws.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | HERE=`pwd` 4 | KEY_ID=${KEY_ID:-alias/pgsodium} 5 | ENCRYPTED_ROOT_KEY_FILE=${ENCRYPTED_ROOT_KEY_FILE:-$HERE/pgsodium_encrypted_root.key} 6 | 7 | if [[ -f "$ENCRYPTED_ROOT_KEY_FILE" ]]; then 8 | aws kms decrypt --ciphertext-blob fileb://$ENCRYPTED_ROOT_KEY_FILE --query Plaintext --output text | base64 --decode | hex 9 | else 10 | aws kms generate-data-key --number-of-bytes=32 --key-id=$KEY_ID --query CiphertextBlob --output text | base64 --decode > $ENCRYPTED_ROOT_KEY_FILE 11 | aws kms decrypt --ciphertext-blob fileb://$ENCRYPTED_ROOT_KEY_FILE --query Plaintext --output text | base64 --decode | hex 12 | fi 13 | 14 | -------------------------------------------------------------------------------- /getkey_scripts/pgsodium_getkey_doppler.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | HERE=`pwd` 4 | KEY_ID=${KEY_ID:-alias/pgsodium} 5 | ENCRYPTED_ROOT_KEY_FILE=${ENCRYPTED_ROOT_KEY_FILE:-$HERE/pgsodium_encrypted_root.key} 6 | 7 | if [ ! -f "$ENCRYPTED_ROOT_KEY_FILE" ]; then 8 | # Expects the Doppler CLI to be installed: https://docs.doppler.com/docs/install-cli 9 | # 10 | # Expects the `DOPPLER_TOKEN` to be available as environment variable which will 11 | # authenticate the Doppler CLI: https://docs.doppler.com/docs/service-tokens 12 | doppler secrets get ENCRYPTION_KEY --plain > $ENCRYPTED_ROOT_KEY_FILE 13 | fi 14 | 15 | cat $ENCRYPTED_ROOT_KEY_FILE 16 | -------------------------------------------------------------------------------- /getkey_scripts/pgsodium_getkey_gcp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | HERE=`pwd` 4 | KEY=${KEY:-pgsodium} 5 | KEYRING=${KEYRING:-pgsodium} 6 | LOCATION=${LOCATION:-global} 7 | ROOT_KEY_FILE=${ROOT_KEY_FILE:-$HERE/pgsodium_encrypted_root.key} 8 | 9 | if [[ -f "$ROOT_KEY_FILE" ]]; then 10 | gcloud kms decrypt \ 11 | --key $KEY \ 12 | --keyring $KEYRING \ 13 | --location $LOCATION \ 14 | --plaintext-file - \ 15 | --ciphertext-file $ROOT_KEY_FILE 16 | else 17 | >&2 cat < $KEY_FILE 6 | fi 7 | cat $KEY_FILE 8 | -------------------------------------------------------------------------------- /getkey_scripts/pgsodium_getkey_zmk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | HERE=`pwd` 4 | KEY=${KEY:-pgsodium} 5 | ROOT_KEY_FILE=${ROOT_KEY_FILE:-$HERE/pgsodium_encrypted_root.key} 6 | 7 | if [[ -f "$ROOT_KEY_FILE" ]]; then 8 | python3 <&2 cat </dev/null; 31 | do sleep 1; 32 | done 33 | 34 | docker exec -it $DB_HOST psql -U "$SU" 35 | -------------------------------------------------------------------------------- /sql/box.sql: -------------------------------------------------------------------------------- 1 | -- # Public Key Cryptography 2 | -- 3 | \pset linestyle unicode 4 | \pset border 2 5 | \pset pager off 6 | create extension if not exists pgsodium; -- pragma:hide 7 | set search_path to pgsodium,public; -- pragma:hide 8 | -- 9 | -- The `box` API uses public key encryption 10 | -- to securely send messages between two parties who only know each 11 | -- others public keys. Each party has a secret key that is used to 12 | -- encrypt messages. 13 | -- 14 | -- [Libsodium Documentation](https://doc.libsodium.org/public-key_cryptography/authenticated_encryption) 15 | -- 16 | -- `crypto_box_new_keypair()` returns a new, randomly generated, pair of 17 | -- keys for public key encryption. The public key can be shared with 18 | -- anyone. The secret key must never be shared. 19 | -- 20 | -- `crypto_box_noncegen()` generates a random nonce which will be used 21 | -- when encrypting messages. For security, each nonce must be used only 22 | -- once, though it is not a secret. The purpose of the nonce is to add 23 | -- randomness to the message so that the same message encrypted multiple 24 | -- times with the same key will produce different ciphertexts. 25 | -- 26 | -- `crypto_box()` encrypts a message using a nonce, the intended 27 | -- recipient's public key and the sender's secret key. The resulting 28 | -- ciphertext can only be decrypted by the intended recipient using their 29 | -- secret key. The nonce must be sent along with the ciphertext. 30 | -- 31 | -- `crypto_box_open()` descrypts a ciphertext encrypted using 32 | -- `crypto_box()`. It takes the ciphertext, nonce, the sender's public 33 | -- key and the recipient's secret key as parameters, and returns the 34 | -- original message. Note that the recipient should ensure that the 35 | -- public key belongs to the sender. 36 | -- 37 | -- 38 | -- Public key encryption requires each party have a pair of keys, one 39 | -- public and one private, and a nonce. The nonce doesn't have to be 40 | -- confidential, but it should never ever be reused with the same 41 | -- key. The easiest way to generate a nonce is to use 42 | -- `crypto_box_noncegen`: 43 | 44 | select crypto_box_new_seed() seed \gset 45 | SELECT pgsodium.crypto_box_noncegen() nonce \gset 46 | 47 | -- Now create a new keypair for both bob and alice. 48 | 49 | SELECT public, secret FROM crypto_box_new_keypair() \gset bob_ 50 | SELECT public, secret FROM crypto_box_seed_new_keypair(:'seed') \gset alice_ 51 | 52 | SELECT pgsodium.crypto_box('hello bob', :'nonce', :'bob_public', :'alice_secret') atob \gset 53 | SELECT :'atob'; 54 | 55 | select pgsodium.crypto_box_open(:'atob', :'nonce', :'alice_public', :'bob_secret'); 56 | 57 | -- Sealed boxes are designed to anonymously send messages to a recipient 58 | -- given its public key. 59 | -- 60 | -- Only the recipient can decrypt these messages, using its private 61 | -- key. While the recipient can verify the integrity of the message, it 62 | -- cannot verify the identity of the sender. 63 | -- 64 | -- A message is encrypted using an ephemeral key pair, whose secret part 65 | -- is destroyed right after the encryption process. 66 | -- 67 | -- Without knowing the secret key used for a given message, the sender 68 | -- cannot decrypt its own message later. And without additional data, a 69 | -- message cannot be correlated with the identity of its sender. 70 | 71 | SELECT pgsodium.crypto_box_seal('bob is your uncle', :'bob_public') sealed \gset 72 | SELECT pgsodium.crypto_box_seal_open(:'sealed', :'bob_public', :'bob_secret'); 73 | 74 | -------------------------------------------------------------------------------- /sql/derive.sql: -------------------------------------------------------------------------------- 1 | \pset linestyle unicode 2 | \pset border 2 3 | \pset pager off 4 | -- # Key Derivation 5 | -- 6 | -- Multiple secret subkeys can be derived from a single master key. 7 | -- 8 | -- Given the master key and a key identifier, a subkey can be 9 | -- deterministically computed. However, given a subkey, an attacker 10 | -- cannot compute the master key nor any other subkeys. 11 | -- 12 | -- The crypto_kdf API can derive up to 2^64 keys from a single master key 13 | -- and context, and individual subkeys can have an arbitrary length 14 | -- between 128 (16 bytes) and 512 bits (64 bytes). 15 | 16 | select pgsodium.derive_key(42); 17 | 18 | select pgsodium.derive_key(42, 64, 'foozbarz'); 19 | 20 | -------------------------------------------------------------------------------- /sql/hash.sql: -------------------------------------------------------------------------------- 1 | -- # Hashing Data 2 | -- 3 | \pset linestyle unicode 4 | \pset border 2 5 | \pset pager off 6 | create extension if not exists pgsodium; -- pragma:hide 7 | set search_path to pgsodium,public; -- pragma:hide 8 | -- 9 | -- libsodium provides functions for "generic" and "short" hashing. 10 | -- 11 | -- Generic hashing is suitable for cryptographic purposes using the BLAKE2b algorithm. 12 | 13 | select pgsodium.crypto_generichash('this is a message'); 14 | 15 | select pgsodium.crypto_generichash_keygen() generickey \gset 16 | 17 | select pgsodium.crypto_generichash('this is a message'::bytea, :'generickey'::bytea); 18 | 19 | -- ## Short Hashing 20 | -- 21 | -- Many applications and programming language implementations were 22 | -- recently found to be vulnerable to denial-of-service (DoS) attacks 23 | -- when a hash function with weak security guarantees, such as 24 | -- MurmurHash3, was used to construct a hash table. 25 | -- 26 | -- To address this, Sodium provides the crypto_shorthash() function, 27 | -- which outputs short but unpredictable (without knowing the secret key) 28 | -- values suitable for picking a list in a hash table for a given key. 29 | -- 30 | -- This function is optimized for short inputs. 31 | -- 32 | -- The output of this function is only 64 bits. Therefore, it should not 33 | -- be considered collision-resistant. 34 | -- 35 | -- Use cases: 36 | -- - Hash tables 37 | -- - Probabilistic data structures, such as Bloom filters 38 | -- - Integrity checking in interactive protocols 39 | 40 | select pgsodium.crypto_shorthash_keygen() shortkey \gset 41 | select pgsodium.crypto_shorthash('this is a message'::bytea, :'shortkey'::bytea); 42 | 43 | -------------------------------------------------------------------------------- /sql/hmac.sql: -------------------------------------------------------------------------------- 1 | \pset linestyle unicode 2 | \pset border 2 3 | \pset pager off 4 | create extension if not exists pgsodium; -- pragma:hide 5 | set search_path to pgsodium,public; -- pragma:hide 6 | -- # Hash-based Message Authentication Codes 7 | -- 8 | -- 9 | -- [https://en.wikipedia.org/wiki/HMAC] 10 | -- 11 | -- In cryptography, an HMAC (sometimes expanded as either keyed-hash 12 | -- message authentication code or hash-based message authentication code) 13 | -- is a specific type of message authentication code (MAC) involving a 14 | -- cryptographic hash function and a secret cryptographic key. As with 15 | -- any MAC, it may be used to simultaneously verify both the data 16 | -- integrity and authenticity of a message. 17 | -- 18 | -- [C API Documentation](https://doc.libsodium.org/advanced/hmac-sha2) 19 | -- 20 | -- pgsodium provides hmacsha512 and hmacsha256, only 512-bit examples are 21 | -- provided below, the 256-bit API is identical but using names like 22 | -- `crypto_auth_hmacsha256_*`. 23 | -- 24 | select pgsodium.crypto_auth_hmacsha512_keygen() hmackey \gset 25 | 26 | select pgsodium.crypto_auth_hmacsha512('this is authentic'::bytea, :'hmackey'::bytea) signature \gset 27 | 28 | select pgsodium.crypto_auth_hmacsha512_verify(:'signature'::bytea, 'this is authentic'::bytea, :'hmackey'::bytea); 29 | 30 | -------------------------------------------------------------------------------- /sql/kdf.sql: -------------------------------------------------------------------------------- 1 | \pset linestyle unicode 2 | \pset border 2 3 | \pset pager off 4 | create extension if not exists pgsodium; -- pragma:hide 5 | set search_path to pgsodium,public; -- pragma:hide 6 | -- # Server Key Management 7 | -- 8 | -- The core feature of pgsodium its its ability to manage encryption keys 9 | -- for you, you so that you never reference a raw encryption key, but 10 | -- instead you reference keys *by ID*. A key id is a UUID that uniquely 11 | -- identifies the key used. An example of using Server Key Management 12 | -- can be found in the section on [Transparent Column 13 | -- Encryption](Transparent_Column_Encryption.md) and is most of the API 14 | -- examples that can take key UUIDs are arguments. 15 | -- 16 | -- ## Create a new Key 17 | -- 18 | -- pgsodium can manage two types of keys, *derived* keys, and *external* 19 | -- keys. Derived keys use libsodium to 20 | -- 21 | -- Server managed keys are created with the `pgsodium.create_key()` 22 | -- function. This function takes a few optional parameters: 23 | -- 24 | -- - `key_type`: The type of key to create, the default is `aead-det`. 25 | -- Can be one of: 26 | -- 27 | -- - `aead-det` 28 | -- - `aead-ietf` 29 | -- - `hmacsha512` 30 | -- - `hmacsha256` 31 | -- - `auth` 32 | -- - `secretbox` 33 | -- - `secretstream` 34 | -- - `shorthash` 35 | -- - `generichash` 36 | -- - `kdf` 37 | -- 38 | -- - `name`: An optional *unique* name for the key. The default is NULL 39 | -- which makes an "anonymous" key. 40 | -- 41 | -- - `derived_key`: An optional raw external key, for example an hmac key 42 | -- from an external service. pgsodium will store this key encrypted 43 | -- with TCE. 44 | -- 45 | -- - `derived_key_nonce`: An optional nonce for the raw key, if none is 46 | -- provided a new random `aead-det` nonce will be generated using 47 | -- `pgsodium.crypto_aead_det_noncegen()`. 48 | -- 49 | -- - `parent_key`: If `raw_key` is not null, then this key id is used to 50 | -- encrypt the raw key. The default is to generate a new `aead-det` 51 | -- key. 52 | -- 53 | -- - `derived_context` 54 | -- 55 | -- - `expires` 56 | -- 57 | -- - `associated_data` 58 | -- 59 | -- `pgsodium.create_key()` returns a new row in the `pgsodium.valid_key` 60 | -- view. For most purposes, you usually just need the new key's ID to 61 | -- start using it. For example, here's a new external shahmac256 key 62 | -- being created and used to verify a payload: 63 | 64 | select pgsodium.crypto_auth_hmacsha256_keygen() key \gset 65 | 66 | select * from pgsodium.create_key('hmacsha256', raw_key:=:'external_key'); 67 | 68 | select id, key_type, parent_key, length(decrypted_raw_key) from pgsodium.decrypted_key where key_type = 'hmacsha256'; 69 | 70 | -------------------------------------------------------------------------------- /sql/kx.sql: -------------------------------------------------------------------------------- 1 | \pset linestyle unicode 2 | \pset border 2 3 | \pset pager off 4 | create extension if not exists pgsodium; -- pragma:hide 5 | set search_path to pgsodium,public; -- pragma:hide 6 | -- # Key Exchange 7 | -- 8 | -- Using the key exchange API, two parties can securely compute a set of 9 | -- shared keys using their peer's public key and their own secret key 10 | 11 | select public, secret from pgsodium.crypto_kx_new_keypair() \gset bob_ 12 | select public, secret from pgsodium.crypto_kx_new_keypair() \gset alice_ 13 | 14 | select tx, rx from pgsodium.crypto_kx_client_session_keys(:'bob_public'::bytea, :'bob_secret'::bytea, :'alice_public'::bytea); 15 | 16 | select tx, rx from pgsodium.crypto_kx_server_session_keys(:'alice_public'::bytea, :'alice_secret'::bytea, :'bob_public'::bytea) 17 | 18 | -------------------------------------------------------------------------------- /sql/pwhash.sql: -------------------------------------------------------------------------------- 1 | -- # Password Hashing 2 | -- 3 | -- Secret keys used to encrypt or sign confidential data have to be 4 | -- chosen from a very large keyspace. 5 | -- 6 | -- However, passwords are usually short, human-generated strings, making 7 | -- dictionary attacks practical. 8 | -- 9 | -- Password hashing functions derive a secret key of any size from a 10 | -- password and salt. 11 | -- 12 | -- - The generated key has the size defined by the application, no 13 | -- matter what the password length is. 14 | -- 15 | -- - The same password hashed with the same parameters will always 16 | -- produce the same output. 17 | -- 18 | -- - The same password hashed with different salts will produce 19 | -- different outputs. 20 | -- 21 | -- - The function deriving a key from a password and salt is CPU 22 | -- intensive and intentionally requires a fair amount of 23 | -- memory. Therefore, it mitigates brute-force attacks by requiring a 24 | -- significant effort to verify each password. 25 | -- 26 | -- Common use cases: 27 | -- 28 | -- - Password storage, or rather storing what it takes to verify a 29 | -- password without having to store the actual password. 30 | -- 31 | -- - Deriving a secret key from a password; for example, for disk 32 | -- encryption. 33 | -- 34 | -- Sodium's high-level crypto_pwhash_* API currently leverages the 35 | -- Argon2id function on all platforms. This can change at any point in 36 | -- time, but it is guaranteed that a given version of libsodium can 37 | -- verify all hashes produced by all previous versions from any 38 | -- platform. Applications don't have to worry about backward 39 | -- compatibility. 40 | 41 | select pgsodium.crypto_pwhash_saltgen() salt \gset 42 | 43 | select pgsodium.crypto_pwhash('Correct Horse Battery Staple', :'salt'); 44 | 45 | select pgsodium.crypto_pwhash_str('Correct Horse Battery Staple') hash \gset 46 | 47 | select pgsodium.crypto_pwhash_str_verify((:'hash')::bytea, 'Correct Horse Battery Staple'); 48 | 49 | -------------------------------------------------------------------------------- /sql/random.sql: -------------------------------------------------------------------------------- 1 | -- # Generating Random Data 2 | -- 3 | \pset linestyle unicode 4 | \pset border 2 5 | \pset pager off 6 | create extension if not exists pgsodium; -- pragma:hide 7 | set search_path to pgsodium,public; -- pragma:hide 8 | -- 9 | -- The library provides a set of functions to generate unpredictable data, suitable for creating secret keys. 10 | -- 11 | -- - On Windows systems, the RtlGenRandom() function is used. 12 | -- - On OpenBSD and Bitrig, the arc4random() function is used. 13 | -- - On recent FreeBSD and Linux kernels, the getrandom system call is used. 14 | -- - On other Unices, the /dev/urandom device is used. 15 | 16 | -- ## `randombytes_random()` 17 | -- 18 | -- Returns a random 32-bit signed integer. 19 | 20 | select pgsodium.randombytes_random() from generate_series(0, 5); 21 | 22 | -- ## `randombytes_uniform(upper_bound interger)` 23 | -- 24 | -- Returns a uniformally distributed random number between zero and the upper bound argument. 25 | 26 | select pgsodium.randombytes_uniform(10) + 3 from generate_series(0, 5); 27 | 28 | -- ## `randombytes_buf(buffer_size integer)` 29 | -- 30 | -- Returns a random buffer of bytes the size of the argument. 31 | 32 | select encode(pgsodium.randombytes_buf(10), 'hex') from generate_series(0, 5); 33 | 34 | -------------------------------------------------------------------------------- /sql/secretbox.sql: -------------------------------------------------------------------------------- 1 | \pset linestyle unicode 2 | \pset border 2 3 | \pset pager off 4 | create extension if not exists pgsodium; -- pragma:hide 5 | set search_path to pgsodium,public; -- pragma:hide 6 | -- # Secret Key Cryptography 7 | 8 | select pgsodium.crypto_secretbox_keygen() key \gset 9 | 10 | select pgsodium.crypto_secretbox_noncegen() nonce \gset 11 | 12 | select pgsodium.crypto_secretbox('bob is your uncle', :'nonce', :'key') secretbox \gset 13 | 14 | select pgsodium.crypto_secretbox_open(:'secretbox', :'nonce', :'key'); 15 | 16 | -------------------------------------------------------------------------------- /sql/tce.sql: -------------------------------------------------------------------------------- 1 | -- # Transparent Column Encryption 2 | \pset linestyle unicode 3 | \pset border 2 4 | \pset pager off 5 | create extension if not exists pgsodium; -- pragma:hide 6 | set search_path to pgsodium,public; -- pragma:hide 7 | -- # Transparent Column Encryption 8 | -- 9 | -- Transparent Column Encryption (TCE) lets you encrypt a column for 10 | -- storage to disk. This pattern is often called "Encryption at Rest". 11 | -- The column is stored encrypted in the postgres database files, as well 12 | -- as log streams and database dumps. TCE uses [Server Key Management]() 13 | -- managed keys by ID. 14 | 15 | -- ## Encrypt Whole Column with One Key ID 16 | -- 17 | -- To encrypt a column, the first step is to create a table. Here is a 18 | -- simple table with one `text` column that will be encrypted. 19 | 20 | DROP TABLE IF EXISTS my_secrets CASCADE; 21 | CREATE TABLE my_secrets ( 22 | secret text 23 | ); 24 | 25 | -- ## Create a new Key ID 26 | -- 27 | -- The next step is create a single key id that will be used to encrypt 28 | -- the column with the `pgsodium.create_key()` function. This new key is 29 | -- used to create a label for the column with a `SECURITY LABEL` command 30 | -- that says which key id should be used to encrypt the table. 31 | 32 | SELECT 'ENCRYPT WITH KEY ID ' || pgsodium.create_key() label \gset 33 | 34 | SECURITY LABEL FOR pgsodium ON COLUMN my_secrets.secret IS :'label'; 35 | 36 | -- You can examine all labeled objects in the standard Postgre catalog 37 | -- table `pg_catalog.pg_seclabel`: 38 | 39 | SELECT objoid::regclass, provider, label FROM pg_seclabel WHERE provider = 'pgsodium'; 40 | 41 | -- ## Insert test data 42 | -- 43 | -- TCE works by dynamically creating an INSERT trigger for the labled 44 | -- table and a view that wraps the table and decrypts the column. To 45 | -- explain, here are some test rows for the table. Note that the 46 | -- inserted secret values are *plaintext*. 47 | 48 | INSERT INTO my_secrets (secret) VALUES ('sekert1'), ('shhhhh'), ('0xABC_my_payment_processor_key'); 49 | 50 | -- ## How Secrets are Stored 51 | 52 | -- Now that there are some secrets in the table, selecting on the table 53 | -- will show that the data is stored in an authenticated encrypted form. 54 | -- The "signature" for authenticated the secret is appended to the value, 55 | -- which is why each value is 32 bytes longer. 56 | 57 | SELECT * FROM my_secrets; 58 | 59 | -- ## Accessing Decrypted Values 60 | 61 | -- When a column is labled with TCE using `SECURITY LABEL`, pgsodium 62 | -- dynamically generate a view that can decrypt rows on the fly. By 63 | -- default this view is named `decrypted_` for the table with 64 | -- any labeled columns. 65 | 66 | SELECT * FROM decrypted_my_secrets; 67 | 68 | -- ## Using per-Row Key Ids, Associated Data, and Nonces 69 | -- 70 | -- The above approach is simple, there is one key to manage and it is 71 | -- used for the whole column. But in many cases you will want finer 72 | -- grained control over which key is applied to which row. For example 73 | -- if you host customer content, you may require a different key for 74 | -- different customers. 75 | -- 76 | -- Furthermore authenticated encryption is useful, but you often have 77 | -- data *associated* with a secret that does not need to be encrypted but 78 | -- does need to be *authenticated* meaning that the associated data is 79 | -- included in the generation of the authentication signature. See the 80 | -- wikipedia page [Authenticated Encryption with Assocaited 81 | -- Data](https://en.wikipedia.org/wiki/Authenticated_encryption#Authenticated_encryption_with_associated_data_(AEAD)) 82 | -- for more information on this technique. pgsodium lets you specify one 83 | -- or more columns that can be associated with a secret as shown below. 84 | -- 85 | -- Finally, a common pattern in encryption is to use a *nonce* value to 86 | -- deduplicate secrets and associated data. Without this nonce, 87 | -- duplicate secrets and associated data would create duplicate encrypted 88 | -- values, and this information can be used by attackers in some 89 | -- situations. 90 | -- 91 | -- To put it all together, lets create another table for customer 92 | -- secrets: 93 | 94 | DROP TABLE IF EXISTS my_customer_secrets CASCADE; 95 | CREATE TABLE my_customer_secrets ( 96 | id bigserial, 97 | secret text, 98 | associated_data json, 99 | owner text NOT NULL, 100 | key_id uuid REFERENCES pgsodium.key(id) DEFAULT (pgsodium.create_key()).id, 101 | nonce bytea DEFAULT pgsodium.crypto_aead_det_noncegen()); 102 | 103 | SECURITY LABEL FOR pgsodium ON TABLE my_customer_secrets IS 'DECRYPT WITH VIEW public.other_name_view'; 104 | 105 | -- Notice that in this case there some new columns, an integer id, an 106 | -- owner, some "associated" data in JSON format, and a nonce. These 107 | -- columns will be used in the security label below. Notice also that 108 | -- the `key_id` and `nonce` columns have defaults, so that if you don't 109 | -- provide a value for them, a new key_id and nonce will by automatically 110 | -- generated. 111 | -- 112 | -- The new label has different syntax than the first example, instead of 113 | -- specifying a `KEY ID` the label specifies a `KEY COLUMN` and some 114 | -- optional `ASSOCIATED` data columns, of which there may be one or more, 115 | -- and a `NONCE` column. 116 | 117 | SECURITY LABEL FOR pgsodium ON COLUMN my_customer_secrets.secret 118 | IS 'ENCRYPT WITH KEY COLUMN key_id ASSOCIATED (id, associated_data, owner) NONCE nonce'; 119 | 120 | -- Insert some test data: 121 | 122 | INSERT INTO my_customer_secrets (secret, associated_data, owner) 123 | VALUES ('blue', '{"type":"color"}', 'bob'), 124 | ('nuts', '{"type":"food"}', 'alice'), 125 | ('fast', '{"type":"car"}', 'mallory'); 126 | 127 | -- As above, secret is now stored en anthenticated encrypted form. The 128 | -- `id`, `associated_data`, and `owner` columns are "mixed in" to the 129 | -- signature that is stored with the secret, so they cannot be forged. 130 | -- Any atttempt to decrypt the secret with inauthentic associated data 131 | -- will fail. 132 | 133 | SELECT secret, associated_data, owner, key_id FROM my_customer_secrets; 134 | 135 | -- Decrypted secret access the view by the name specified in the table label above. 136 | 137 | SELECT decrypted_secret, associated_data, owner, key_id FROM other_name_view; 138 | -------------------------------------------------------------------------------- /src/auth.c: -------------------------------------------------------------------------------- 1 | #include "pgsodium.h" 2 | 3 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_auth); 4 | Datum 5 | pgsodium_crypto_auth (PG_FUNCTION_ARGS) 6 | { 7 | bytea *message; 8 | bytea *key; 9 | int result_size; 10 | bytea *result; 11 | 12 | ERRORIF (PG_ARGISNULL (0), "%s: message cannot be NULL"); 13 | ERRORIF (PG_ARGISNULL (1), "%s: key cannot be NULL"); 14 | 15 | message = PG_GETARG_BYTEA_PP (0); 16 | key = PG_GETARG_BYTEA_PP (1); 17 | 18 | ERRORIF (VARSIZE_ANY_EXHDR (key) != crypto_auth_KEYBYTES, 19 | "%s: invalid key"); 20 | result_size = VARHDRSZ + crypto_auth_BYTES; 21 | result = _pgsodium_zalloc_bytea (result_size); 22 | crypto_auth ( 23 | PGSODIUM_UCHARDATA (result), 24 | PGSODIUM_UCHARDATA_ANY (message), 25 | VARSIZE_ANY_EXHDR (message), 26 | PGSODIUM_UCHARDATA_ANY (key)); 27 | PG_RETURN_BYTEA_P (result); 28 | } 29 | 30 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_auth_by_id); 31 | Datum 32 | pgsodium_crypto_auth_by_id (PG_FUNCTION_ARGS) 33 | { 34 | bytea *message; 35 | unsigned long long key_id; 36 | bytea *context; 37 | bytea *key; 38 | int result_size; 39 | bytea *result; 40 | 41 | ERRORIF (PG_ARGISNULL (0), "%s: message cannot be NULL"); 42 | ERRORIF (PG_ARGISNULL (1), "%s: key id cannot be NULL"); 43 | ERRORIF (PG_ARGISNULL (2), "%s: key context cannot be NULL"); 44 | 45 | message = PG_GETARG_BYTEA_PP (0); 46 | key_id = PG_GETARG_INT64 (1); 47 | context = PG_GETARG_BYTEA_PP (2); 48 | 49 | key = pgsodium_derive_helper (key_id, crypto_auth_KEYBYTES, context); 50 | ERRORIF (VARSIZE_ANY_EXHDR (key) != crypto_auth_KEYBYTES, 51 | "%s: invalid key"); 52 | result_size = VARHDRSZ + crypto_auth_BYTES; 53 | result = _pgsodium_zalloc_bytea (result_size); 54 | crypto_auth ( 55 | PGSODIUM_UCHARDATA (result), 56 | PGSODIUM_UCHARDATA_ANY (message), 57 | VARSIZE_ANY_EXHDR (message), 58 | PGSODIUM_UCHARDATA_ANY (key)); 59 | PG_RETURN_BYTEA_P (result); 60 | } 61 | 62 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_auth_verify); 63 | Datum 64 | pgsodium_crypto_auth_verify (PG_FUNCTION_ARGS) 65 | { 66 | int success; 67 | bytea *mac; 68 | bytea *message; 69 | bytea *key; 70 | 71 | ERRORIF (PG_ARGISNULL (0), "%s: signature cannot be NULL"); 72 | ERRORIF (PG_ARGISNULL (1), "%s: message cannot be NULL"); 73 | ERRORIF (PG_ARGISNULL (2), "%s: key cannot be NULL"); 74 | 75 | mac = PG_GETARG_BYTEA_PP (0); 76 | message = PG_GETARG_BYTEA_PP (1); 77 | key = PG_GETARG_BYTEA_PP (2); 78 | 79 | ERRORIF (VARSIZE_ANY_EXHDR (mac) != crypto_auth_BYTES, "%s: invalid mac"); 80 | ERRORIF (VARSIZE_ANY_EXHDR (key) != crypto_auth_KEYBYTES, 81 | "%s: invalid key"); 82 | success = 83 | crypto_auth_verify ( 84 | PGSODIUM_UCHARDATA_ANY (mac), 85 | PGSODIUM_UCHARDATA_ANY (message), 86 | VARSIZE_ANY_EXHDR (message), 87 | PGSODIUM_UCHARDATA_ANY (key)); 88 | PG_RETURN_BOOL (success == 0); 89 | } 90 | 91 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_auth_verify_by_id); 92 | Datum 93 | pgsodium_crypto_auth_verify_by_id (PG_FUNCTION_ARGS) 94 | { 95 | int success; 96 | bytea *mac; 97 | bytea *message; 98 | unsigned long long key_id; 99 | bytea *context; 100 | bytea *key; 101 | 102 | ERRORIF (PG_ARGISNULL (0), "%s: signature cannot be NULL"); 103 | ERRORIF (PG_ARGISNULL (1), "%s: message cannot be NULL"); 104 | ERRORIF (PG_ARGISNULL (2), "%s: key id cannot be NULL"); 105 | ERRORIF (PG_ARGISNULL (3), "%s: key context cannot be NULL"); 106 | 107 | mac = PG_GETARG_BYTEA_PP (0); 108 | message = PG_GETARG_BYTEA_PP (1); 109 | key_id = PG_GETARG_INT64 (2); 110 | context = PG_GETARG_BYTEA_PP (3); 111 | 112 | key = pgsodium_derive_helper (key_id, crypto_secretbox_KEYBYTES, context); 113 | 114 | ERRORIF (VARSIZE_ANY_EXHDR (mac) != crypto_auth_BYTES, "%s: invalid mac"); 115 | ERRORIF (VARSIZE_ANY_EXHDR (key) != crypto_auth_KEYBYTES, 116 | "%s: invalid key"); 117 | success = 118 | crypto_auth_verify ( 119 | PGSODIUM_UCHARDATA_ANY (mac), 120 | PGSODIUM_UCHARDATA_ANY (message), 121 | VARSIZE_ANY_EXHDR (message), 122 | PGSODIUM_UCHARDATA_ANY (key)); 123 | PG_RETURN_BOOL (success == 0); 124 | } 125 | 126 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_auth_keygen); 127 | Datum 128 | pgsodium_crypto_auth_keygen (PG_FUNCTION_ARGS) 129 | { 130 | size_t result_size = VARHDRSZ + crypto_auth_KEYBYTES; 131 | bytea *result = _pgsodium_zalloc_bytea (result_size); 132 | crypto_secretbox_keygen (PGSODIUM_UCHARDATA (result)); 133 | PG_RETURN_BYTEA_P (result); 134 | } 135 | -------------------------------------------------------------------------------- /src/crypto_aead_det_xchacha20.c: -------------------------------------------------------------------------------- 1 | /* BSD 2-Clause License */ 2 | 3 | /* Copyright (c) 2020, Frank Denis */ 4 | /* All rights reserved. */ 5 | 6 | /* Redistribution and use in source and binary forms, with or without */ 7 | /* modification, are permitted provided that the following conditions are met: */ 8 | 9 | /* * Redistributions of source code must retain the above copyright notice, this */ 10 | /* list of conditions and the following disclaimer. */ 11 | 12 | /* * Redistributions in binary form must reproduce the above copyright notice, */ 13 | /* this list of conditions and the following disclaimer in the documentation */ 14 | /* and/or other materials provided with the distribution. */ 15 | 16 | /* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" */ 17 | /* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE */ 18 | /* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ 19 | /* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE */ 20 | /* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL */ 21 | /* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR */ 22 | /* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER */ 23 | /* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, */ 24 | /* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE */ 25 | /* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 26 | 27 | #include 28 | #include 29 | 30 | #include "crypto_aead_det_xchacha20.h" 31 | 32 | static void 33 | s2v_dbl256 (unsigned char d[32]) 34 | { 35 | unsigned char t[32]; 36 | unsigned char mask; 37 | size_t i; 38 | 39 | memcpy (t, d, 32); 40 | for (i = 0; i < 32; i++) 41 | { 42 | t[i] = (unsigned char) (t[i] << 1); 43 | } 44 | for (i = 31; i != 0; i--) 45 | { 46 | t[i - 1] |= d[i] >> 7; 47 | } 48 | mask = ~((d[0] >> 7) - 1); 49 | t[30] ^= (0x04 & mask); 50 | t[31] ^= (0x25 & mask); 51 | memcpy (d, t, 32); 52 | } 53 | 54 | static inline void 55 | s2v_xor (unsigned char *d, const unsigned char *h, size_t len) 56 | { 57 | size_t i; 58 | 59 | for (i = 0; i < len; i++) 60 | { 61 | d[i] ^= h[i]; 62 | } 63 | } 64 | 65 | static void 66 | s2v (unsigned char iv[crypto_aead_det_xchacha20_ABYTES], 67 | const unsigned char *m, size_t mlen, const unsigned char *ad, size_t adlen, 68 | const unsigned char *nonce, size_t noncelen, const unsigned char ka[32]) 69 | { 70 | static const unsigned char zero[crypto_aead_det_xchacha20_ABYTES] = { 0 }; 71 | crypto_generichash_state st; 72 | unsigned char d[32]; 73 | 74 | crypto_generichash (d, sizeof d, zero, sizeof zero, ka, sizeof d); 75 | 76 | if (ad != NULL && adlen > 0) 77 | { 78 | s2v_dbl256 (d); 79 | crypto_generichash (iv, crypto_aead_det_xchacha20_ABYTES, ad, adlen, 80 | ka, 32); 81 | s2v_xor (d, iv, sizeof d); 82 | } 83 | if (nonce != NULL && noncelen > 0) 84 | { 85 | s2v_dbl256 (d); 86 | crypto_generichash (iv, crypto_aead_det_xchacha20_ABYTES, nonce, 87 | noncelen, ka, 32); 88 | s2v_xor (d, iv, sizeof d); 89 | } 90 | 91 | crypto_generichash_init (&st, ka, 32, crypto_aead_det_xchacha20_ABYTES); 92 | if (mlen >= crypto_aead_det_xchacha20_ABYTES) 93 | { 94 | crypto_generichash_update (&st, m, 95 | mlen - crypto_aead_det_xchacha20_ABYTES); 96 | s2v_xor (d, &m[mlen - crypto_aead_det_xchacha20_ABYTES], 97 | crypto_aead_det_xchacha20_KEYBYTES); 98 | } 99 | else 100 | { 101 | s2v_dbl256 (d); 102 | s2v_xor (d, m, mlen); 103 | d[mlen] ^= 0x80; 104 | } 105 | crypto_generichash_update (&st, d, sizeof d); 106 | crypto_generichash_final (&st, iv, crypto_aead_det_xchacha20_ABYTES); 107 | } 108 | 109 | int 110 | crypto_aead_det_xchacha20_encrypt_detached (unsigned char *c, 111 | unsigned char mac[crypto_aead_det_xchacha20_ABYTES], 112 | const unsigned char *m, size_t mlen, const unsigned char *ad, size_t adlen, 113 | const unsigned char *nonce, 114 | const unsigned char k[crypto_aead_det_xchacha20_KEYBYTES]) 115 | { 116 | unsigned char subkeys[64], *ka = &subkeys[0], *ke = &subkeys[32]; 117 | 118 | crypto_generichash (subkeys, sizeof subkeys, NULL, 0, k, 119 | crypto_aead_det_xchacha20_KEYBYTES); 120 | s2v (mac, m, mlen, ad, adlen, nonce, crypto_aead_det_xchacha20_NONCEBYTES, 121 | ka); 122 | crypto_stream_xchacha20_xor (c, m, mlen, mac, ke); 123 | 124 | return 0; 125 | } 126 | 127 | int 128 | crypto_aead_det_xchacha20_decrypt_detached (unsigned char *m, 129 | const unsigned char *c, size_t clen, 130 | const unsigned char mac[crypto_aead_det_xchacha20_ABYTES], 131 | const unsigned char *ad, size_t adlen, const unsigned char *nonce, 132 | const unsigned char k[crypto_aead_det_xchacha20_KEYBYTES]) 133 | { 134 | unsigned char subkeys[64], *ka = &subkeys[0], *ke = &subkeys[32]; 135 | unsigned char computed_mac[crypto_aead_det_xchacha20_ABYTES]; 136 | const size_t mlen = clen; 137 | 138 | crypto_generichash (subkeys, sizeof subkeys, NULL, 0, k, 139 | crypto_aead_det_xchacha20_KEYBYTES); 140 | crypto_stream_xchacha20_xor (m, c, clen, mac, ke); 141 | s2v (computed_mac, m, mlen, ad, adlen, nonce, 142 | crypto_aead_det_xchacha20_NONCEBYTES, ka); 143 | if (sodium_memcmp (mac, computed_mac, 144 | crypto_aead_det_xchacha20_ABYTES) != 0) 145 | { 146 | memset (m, 0, mlen); 147 | return -1; 148 | } 149 | return 0; 150 | } 151 | 152 | int 153 | crypto_aead_det_xchacha20_encrypt (unsigned char *c, const unsigned char *m, 154 | size_t mlen, const unsigned char *ad, size_t adlen, 155 | const unsigned char *nonce, 156 | const unsigned char k[crypto_aead_det_xchacha20_KEYBYTES]) 157 | { 158 | return crypto_aead_det_xchacha20_encrypt_detached (c, c + mlen, m, mlen, 159 | ad, adlen, nonce, k); 160 | } 161 | 162 | int 163 | crypto_aead_det_xchacha20_decrypt (unsigned char *m, const unsigned char *c, 164 | size_t clen, const unsigned char *ad, size_t adlen, 165 | const unsigned char *nonce, 166 | const unsigned char k[crypto_aead_det_xchacha20_KEYBYTES]) 167 | { 168 | size_t mlen; 169 | 170 | if (clen < crypto_aead_det_xchacha20_ABYTES) 171 | { 172 | return -1; 173 | } 174 | mlen = clen - crypto_aead_det_xchacha20_ABYTES; 175 | 176 | return crypto_aead_det_xchacha20_decrypt_detached (m, c, mlen, c + mlen, 177 | ad, adlen, nonce, k); 178 | } 179 | 180 | void 181 | crypto_aead_det_xchacha20_keygen (unsigned char 182 | k[crypto_aead_det_xchacha20_KEYBYTES]) 183 | { 184 | randombytes_buf (k, crypto_aead_det_xchacha20_KEYBYTES); 185 | } 186 | -------------------------------------------------------------------------------- /src/crypto_aead_det_xchacha20.h: -------------------------------------------------------------------------------- 1 | /* BSD 2-Clause License */ 2 | 3 | /* Copyright (c) 2020, Frank Denis */ 4 | /* All rights reserved. */ 5 | 6 | /* Redistribution and use in source and binary forms, with or without */ 7 | /* modification, are permitted provided that the following conditions are met: */ 8 | 9 | /* * Redistributions of source code must retain the above copyright notice, this */ 10 | /* list of conditions and the following disclaimer. */ 11 | 12 | /* * Redistributions in binary form must reproduce the above copyright notice, */ 13 | /* this list of conditions and the following disclaimer in the documentation */ 14 | /* and/or other materials provided with the distribution. */ 15 | 16 | /* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" */ 17 | /* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE */ 18 | /* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ 19 | /* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE */ 20 | /* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL */ 21 | /* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR */ 22 | /* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER */ 23 | /* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, */ 24 | /* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE */ 25 | /* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 26 | 27 | #ifndef crypto_aead_det_xchacha20_H 28 | #define crypto_aead_det_xchacha20_H 29 | 30 | #ifdef __cplusplus 31 | extern "C" 32 | { 33 | #endif 34 | 35 | #include 36 | 37 | #define crypto_aead_det_xchacha20_KEYBYTES 32 38 | #define crypto_aead_det_xchacha20_ABYTES 32 39 | #define crypto_aead_det_xchacha20_NONCEBYTES 16 40 | 41 | int crypto_aead_det_xchacha20_encrypt_detached (unsigned char *c, 42 | unsigned char mac[crypto_aead_det_xchacha20_ABYTES], 43 | const unsigned char *m, size_t mlen, const unsigned char *ad, 44 | size_t adlen, const unsigned char *nonce, 45 | const unsigned char k[crypto_aead_det_xchacha20_KEYBYTES]); 46 | 47 | int crypto_aead_det_xchacha20_decrypt_detached (unsigned char *m, 48 | const unsigned char *c, size_t clen, 49 | const unsigned char mac[crypto_aead_det_xchacha20_ABYTES], 50 | const unsigned char *ad, size_t adlen, const unsigned char *nonce, 51 | const unsigned char k[crypto_aead_det_xchacha20_KEYBYTES]); 52 | 53 | int crypto_aead_det_xchacha20_encrypt (unsigned char *c, 54 | const unsigned char *m, size_t mlen, const unsigned char *ad, 55 | size_t adlen, const unsigned char *nonce, 56 | const unsigned char k[crypto_aead_det_xchacha20_KEYBYTES]); 57 | 58 | int crypto_aead_det_xchacha20_decrypt (unsigned char *m, 59 | const unsigned char *c, size_t clen, const unsigned char *ad, 60 | size_t adlen, const unsigned char *nonce, 61 | const unsigned char k[crypto_aead_det_xchacha20_KEYBYTES]); 62 | 63 | void crypto_aead_det_xchacha20_keygen (unsigned char 64 | k[crypto_aead_det_xchacha20_KEYBYTES]); 65 | 66 | #ifdef __cplusplus 67 | } 68 | #endif 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /src/derive.c: -------------------------------------------------------------------------------- 1 | /* doctest/derive 2 | \pset linestyle unicode 3 | \pset border 2 4 | \pset pager off 5 | -- # Key Derivation 6 | -- 7 | -- Multiple secret subkeys can be derived from a single master key. 8 | -- 9 | -- Given the master key and a key identifier, a subkey can be 10 | -- deterministically computed. However, given a subkey, an attacker 11 | -- cannot compute the master key nor any other subkeys. 12 | -- 13 | -- The crypto_kdf API can derive up to 2^64 keys from a single master key 14 | -- and context, and individual subkeys can have an arbitrary length 15 | -- between 128 (16 bytes) and 512 bits (64 bytes). 16 | 17 | select pgsodium.derive_key(42); 18 | 19 | select pgsodium.derive_key(42, 64, 'foozbarz'); 20 | 21 | */ 22 | #include "pgsodium.h" 23 | 24 | PG_FUNCTION_INFO_V1 (pgsodium_derive); 25 | Datum 26 | pgsodium_derive (PG_FUNCTION_ARGS) 27 | { 28 | unsigned long long subkey_id; 29 | size_t subkey_size; 30 | bytea *context; 31 | 32 | ERRORIF (PG_ARGISNULL (0), "%s: key id cannot be NULL"); 33 | ERRORIF (PG_ARGISNULL (1), "%s: key size cannot be NULL"); 34 | ERRORIF (PG_ARGISNULL (2), "%s: key context cannot be NULL"); 35 | 36 | subkey_id = PG_GETARG_INT64 (0); 37 | subkey_size = PG_GETARG_UINT32 (1); 38 | context = PG_GETARG_BYTEA_PP (2); 39 | 40 | PG_RETURN_BYTEA_P (pgsodium_derive_helper (subkey_id, subkey_size, 41 | context)); 42 | } 43 | -------------------------------------------------------------------------------- /src/hash.c: -------------------------------------------------------------------------------- 1 | /* doctest/hash 2 | -- # Hashing Data 3 | -- 4 | \pset linestyle unicode 5 | \pset border 2 6 | \pset pager off 7 | create extension if not exists pgsodium; -- pragma:hide 8 | set search_path to pgsodium,public; -- pragma:hide 9 | -- 10 | -- libsodium provides functions for "generic" and "short" hashing. 11 | -- 12 | -- Generic hashing is suitable for cryptographic purposes using the BLAKE2b algorithm. 13 | 14 | select pgsodium.crypto_generichash('this is a message'); 15 | 16 | select pgsodium.crypto_generichash_keygen() generickey \gset 17 | 18 | select pgsodium.crypto_generichash('this is a message'::bytea, :'generickey'::bytea); 19 | 20 | -- ## Short Hashing 21 | -- 22 | -- Many applications and programming language implementations were 23 | -- recently found to be vulnerable to denial-of-service (DoS) attacks 24 | -- when a hash function with weak security guarantees, such as 25 | -- MurmurHash3, was used to construct a hash table. 26 | -- 27 | -- To address this, Sodium provides the crypto_shorthash() function, 28 | -- which outputs short but unpredictable (without knowing the secret key) 29 | -- values suitable for picking a list in a hash table for a given key. 30 | -- 31 | -- This function is optimized for short inputs. 32 | -- 33 | -- The output of this function is only 64 bits. Therefore, it should not 34 | -- be considered collision-resistant. 35 | -- 36 | -- Use cases: 37 | -- - Hash tables 38 | -- - Probabilistic data structures, such as Bloom filters 39 | -- - Integrity checking in interactive protocols 40 | 41 | select pgsodium.crypto_shorthash_keygen() shortkey \gset 42 | select pgsodium.crypto_shorthash('this is a message'::bytea, :'shortkey'::bytea); 43 | 44 | */ 45 | 46 | #include "pgsodium.h" 47 | 48 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_generichash_keygen); 49 | Datum 50 | pgsodium_crypto_generichash_keygen (PG_FUNCTION_ARGS) 51 | { 52 | size_t result_size = VARHDRSZ + crypto_generichash_KEYBYTES; 53 | bytea *result = _pgsodium_zalloc_bytea (result_size); 54 | randombytes_buf (VARDATA (result), crypto_generichash_KEYBYTES); 55 | PG_RETURN_BYTEA_P (result); 56 | } 57 | 58 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_generichash); 59 | Datum 60 | pgsodium_crypto_generichash (PG_FUNCTION_ARGS) 61 | { 62 | bytea *data; 63 | bytea *result; 64 | bytea *keyarg; 65 | unsigned char *key = NULL; 66 | size_t keylen = 0; 67 | size_t result_size; 68 | 69 | ERRORIF (PG_ARGISNULL (0), "%s: data cannot be NULL"); 70 | 71 | data = PG_GETARG_BYTEA_PP (0); 72 | if (!PG_ARGISNULL (1)) 73 | { 74 | keyarg = PG_GETARG_BYTEA_PP (1); 75 | key = PGSODIUM_UCHARDATA_ANY (keyarg); 76 | keylen = VARSIZE_ANY_EXHDR (keyarg); 77 | ERRORIF (keylen < crypto_generichash_KEYBYTES_MIN || 78 | keylen > crypto_generichash_KEYBYTES_MAX, "%s: invalid key"); 79 | } 80 | result_size = VARHDRSZ + crypto_generichash_BYTES; 81 | result = _pgsodium_zalloc_bytea (result_size); 82 | crypto_generichash ( 83 | PGSODIUM_UCHARDATA (result), 84 | crypto_generichash_BYTES, 85 | PGSODIUM_UCHARDATA_ANY (data), 86 | VARSIZE_ANY_EXHDR (data), 87 | key, 88 | keylen); 89 | PG_RETURN_BYTEA_P (result); 90 | } 91 | 92 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_shorthash_keygen); 93 | Datum 94 | pgsodium_crypto_shorthash_keygen (PG_FUNCTION_ARGS) 95 | { 96 | size_t result_size = VARHDRSZ + crypto_shorthash_KEYBYTES; 97 | bytea *result = _pgsodium_zalloc_bytea (result_size); 98 | crypto_shorthash_keygen (PGSODIUM_UCHARDATA (result)); 99 | PG_RETURN_BYTEA_P (result); 100 | } 101 | 102 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_shorthash); 103 | Datum 104 | pgsodium_crypto_shorthash (PG_FUNCTION_ARGS) 105 | { 106 | bytea *data; 107 | bytea *result; 108 | bytea *key; 109 | int result_size = VARHDRSZ + crypto_shorthash_BYTES; 110 | 111 | ERRORIF (PG_ARGISNULL (0), "%s: data cannot be NULL"); 112 | ERRORIF (PG_ARGISNULL (1), "%s: key cannot be NULL"); 113 | 114 | data = PG_GETARG_BYTEA_PP (0); 115 | key = PG_GETARG_BYTEA_PP (1); 116 | 117 | ERRORIF (VARSIZE_ANY_EXHDR (key) != crypto_shorthash_KEYBYTES, 118 | "%s: invalid key"); 119 | result = _pgsodium_zalloc_bytea (result_size); 120 | crypto_shorthash ( 121 | PGSODIUM_UCHARDATA (result), 122 | PGSODIUM_UCHARDATA (data), 123 | VARSIZE_ANY_EXHDR (data), 124 | PGSODIUM_UCHARDATA (key)); 125 | PG_RETURN_BYTEA_P (result); 126 | } 127 | 128 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_generichash_by_id); 129 | Datum 130 | pgsodium_crypto_generichash_by_id (PG_FUNCTION_ARGS) 131 | { 132 | bytea *data; 133 | bytea *result; 134 | bytea *keyarg; 135 | bytea *context; 136 | unsigned char *key = NULL; 137 | size_t keylen = 0; 138 | size_t result_size; 139 | 140 | ERRORIF (PG_ARGISNULL (0), "%s: data cannot be NULL"); 141 | 142 | data = PG_GETARG_BYTEA_PP (0); 143 | if (!PG_ARGISNULL (1)) 144 | { 145 | unsigned long long key_id = PG_GETARG_INT64 (1); 146 | ERRORIF (PG_ARGISNULL (2), "%s: key context be NULL"); 147 | context = PG_GETARG_BYTEA_PP (2); 148 | keyarg = 149 | pgsodium_derive_helper (key_id, crypto_generichash_KEYBYTES, 150 | context); 151 | key = PGSODIUM_UCHARDATA_ANY (keyarg); 152 | keylen = VARSIZE_ANY_EXHDR (keyarg); 153 | ERRORIF (keylen < crypto_generichash_KEYBYTES_MIN || 154 | keylen > crypto_generichash_KEYBYTES_MAX, "%s: invalid key"); 155 | } 156 | result_size = VARHDRSZ + crypto_generichash_BYTES; 157 | result = _pgsodium_zalloc_bytea (result_size); 158 | crypto_generichash ( 159 | PGSODIUM_UCHARDATA (result), 160 | crypto_generichash_BYTES, 161 | PGSODIUM_UCHARDATA_ANY (data), 162 | VARSIZE_ANY_EXHDR (data), 163 | key, 164 | keylen); 165 | PG_RETURN_BYTEA_P (result); 166 | } 167 | 168 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_shorthash_by_id); 169 | Datum 170 | pgsodium_crypto_shorthash_by_id (PG_FUNCTION_ARGS) 171 | { 172 | bytea *data; 173 | bytea *result; 174 | bytea *key; 175 | bytea *context; 176 | uint64_t key_id; 177 | int result_size = VARHDRSZ + crypto_shorthash_BYTES; 178 | 179 | ERRORIF (PG_ARGISNULL (0), "%s: data cannot be NULL"); 180 | ERRORIF (PG_ARGISNULL (1), "%s: key id cannot be NULL"); 181 | ERRORIF (PG_ARGISNULL (2), "%s: key context cannot be NULL"); 182 | 183 | data = PG_GETARG_BYTEA_PP (0); 184 | key_id = PG_GETARG_INT64 (1); 185 | context = PG_GETARG_BYTEA_PP (2); 186 | key = pgsodium_derive_helper (key_id, crypto_shorthash_KEYBYTES, context); 187 | result = _pgsodium_zalloc_bytea (result_size); 188 | crypto_shorthash ( 189 | PGSODIUM_UCHARDATA (result), 190 | PGSODIUM_UCHARDATA_ANY (data), 191 | VARSIZE_ANY_EXHDR (data), 192 | PGSODIUM_UCHARDATA_ANY (key)); 193 | PG_RETURN_BYTEA_P (result); 194 | } 195 | -------------------------------------------------------------------------------- /src/helpers.c: -------------------------------------------------------------------------------- 1 | #include "pgsodium.h" 2 | 3 | PG_FUNCTION_INFO_V1 (pgsodium_cmp); 4 | Datum 5 | pgsodium_cmp (PG_FUNCTION_ARGS) 6 | { 7 | int i = 0; 8 | int m = 0; 9 | 10 | bytea *X = PG_GETARG_BYTEA_PP (0); 11 | bytea *Y = PG_GETARG_BYTEA_PP (1); 12 | size_t xlen = VARSIZE_ANY (X); 13 | size_t ylen = VARSIZE_ANY (Y); 14 | char *x = VARDATA_ANY (X); 15 | char *y = VARDATA_ANY (Y); 16 | 17 | if (xlen != ylen) 18 | PG_RETURN_BOOL (false); 19 | 20 | for (i = 0; i < xlen; i++) 21 | m |= x[i] ^ y[i]; 22 | 23 | PG_RETURN_BOOL (m == 0); 24 | } 25 | 26 | PG_FUNCTION_INFO_V1 (pgsodium_sodium_bin2base64); 27 | Datum 28 | pgsodium_sodium_bin2base64 (PG_FUNCTION_ARGS) 29 | { 30 | bytea *bin; 31 | size_t bin_size; 32 | size_t text_size; 33 | text *base64; 34 | 35 | ERRORIF (PG_ARGISNULL (0), "%s: bin cannot be NULL"); 36 | 37 | bin = PG_GETARG_BYTEA_PP (0); 38 | bin_size = VARSIZE_ANY_EXHDR (bin); 39 | text_size = sodium_base64_ENCODED_LEN ( 40 | bin_size, 41 | sodium_base64_VARIANT_URLSAFE_NO_PADDING); 42 | base64 = (text *) _pgsodium_zalloc_text (text_size + VARHDRSZ); 43 | 44 | sodium_bin2base64 ( 45 | PGSODIUM_CHARDATA (base64), 46 | text_size, 47 | PGSODIUM_UCHARDATA_ANY (bin), 48 | bin_size, 49 | sodium_base64_VARIANT_URLSAFE_NO_PADDING); 50 | PG_RETURN_TEXT_P (base64); 51 | } 52 | 53 | PG_FUNCTION_INFO_V1 (pgsodium_sodium_base642bin); 54 | Datum 55 | pgsodium_sodium_base642bin (PG_FUNCTION_ARGS) 56 | { 57 | text *base64; 58 | size_t base64_size; 59 | size_t max_bin_size; 60 | bytea *bin; 61 | size_t bin_size; 62 | int success; 63 | 64 | ERRORIF (PG_ARGISNULL (0), "%s: base64 cannot be NULL"); 65 | 66 | base64 = PG_GETARG_TEXT_PP (0); 67 | base64_size = VARSIZE_ANY_EXHDR (base64); 68 | max_bin_size = ((base64_size + 1) / 4) * 3; 69 | bin = _pgsodium_zalloc_bytea (max_bin_size + VARHDRSZ); 70 | 71 | success = sodium_base642bin ( 72 | PGSODIUM_UCHARDATA (bin), 73 | max_bin_size, 74 | PGSODIUM_CHARDATA_ANY (base64), 75 | base64_size, 76 | "", 77 | &bin_size, 78 | NULL, 79 | sodium_base64_VARIANT_URLSAFE_NO_PADDING); 80 | ERRORIF (success != 0, "%s: sodium_base642bin() failed"); 81 | SET_VARSIZE (bin, bin_size + VARHDRSZ); 82 | PG_RETURN_BYTEA_P (bin); 83 | } 84 | -------------------------------------------------------------------------------- /src/kdf.c: -------------------------------------------------------------------------------- 1 | /* doctest/kdf 2 | \pset linestyle unicode 3 | \pset border 2 4 | \pset pager off 5 | create extension if not exists pgsodium; -- pragma:hide 6 | set search_path to pgsodium,public; -- pragma:hide 7 | -- # Server Key Management 8 | -- 9 | -- The core feature of pgsodium its its ability to manage encryption keys 10 | -- for you, you so that you never reference a raw encryption key, but 11 | -- instead you reference keys *by ID*. A key id is a UUID that uniquely 12 | -- identifies the key used. An example of using Server Key Management 13 | -- can be found in the section on [Transparent Column 14 | -- Encryption](Transparent_Column_Encryption.md) and is most of the API 15 | -- examples that can take key UUIDs are arguments. 16 | -- 17 | -- ## Create a new Key 18 | -- 19 | -- pgsodium can manage two types of keys, *derived* keys, and *external* 20 | -- keys. Derived keys use libsodium to 21 | -- 22 | -- Server managed keys are created with the `pgsodium.create_key()` 23 | -- function. This function takes a few optional parameters: 24 | -- 25 | -- - `key_type`: The type of key to create, the default is `aead-det`. 26 | -- Can be one of: 27 | -- 28 | -- - `aead-det` 29 | -- - `aead-ietf` 30 | -- - `hmacsha512` 31 | -- - `hmacsha256` 32 | -- - `auth` 33 | -- - `secretbox` 34 | -- - `secretstream` 35 | -- - `shorthash` 36 | -- - `generichash` 37 | -- - `kdf` 38 | -- 39 | -- - `name`: An optional *unique* name for the key. The default is NULL 40 | -- which makes an "anonymous" key. 41 | -- 42 | -- - `derived_key`: An optional raw external key, for example an hmac key 43 | -- from an external service. pgsodium will store this key encrypted 44 | -- with TCE. 45 | -- 46 | -- - `derived_key_nonce`: An optional nonce for the raw key, if none is 47 | -- provided a new random `aead-det` nonce will be generated using 48 | -- `pgsodium.crypto_aead_det_noncegen()`. 49 | -- 50 | -- - `parent_key`: If `raw_key` is not null, then this key id is used to 51 | -- encrypt the raw key. The default is to generate a new `aead-det` 52 | -- key. 53 | -- 54 | -- - `derived_context` 55 | -- 56 | -- - `expires` 57 | -- 58 | -- - `associated_data` 59 | -- 60 | -- `pgsodium.create_key()` returns a new row in the `pgsodium.valid_key` 61 | -- view. For most purposes, you usually just need the new key's ID to 62 | -- start using it. For example, here's a new external shahmac256 key 63 | -- being created and used to verify a payload: 64 | 65 | select pgsodium.crypto_auth_hmacsha256_keygen() key \gset 66 | 67 | select * from pgsodium.create_key('hmacsha256', raw_key:=:'external_key'); 68 | 69 | select id, key_type, parent_key, length(decrypted_raw_key) from pgsodium.decrypted_key where key_type = 'hmacsha256'; 70 | 71 | */ 72 | #include "pgsodium.h" 73 | 74 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_kdf_keygen); 75 | Datum 76 | pgsodium_crypto_kdf_keygen (PG_FUNCTION_ARGS) 77 | { 78 | size_t result_size = VARHDRSZ + crypto_kdf_KEYBYTES; 79 | bytea *result = _pgsodium_zalloc_bytea (result_size); 80 | crypto_kdf_keygen (PGSODIUM_UCHARDATA (result)); 81 | PG_RETURN_BYTEA_P (result); 82 | } 83 | 84 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_kdf_derive_from_key); 85 | Datum 86 | pgsodium_crypto_kdf_derive_from_key (PG_FUNCTION_ARGS) 87 | { 88 | size_t subkey_size; 89 | size_t result_size; 90 | unsigned long long subkey_id; 91 | bytea *context; 92 | bytea *primary_key; 93 | bytea *result; 94 | 95 | ERRORIF (PG_ARGISNULL (0), "%s: subkey size cannot be NULL"); 96 | ERRORIF (PG_ARGISNULL (1), "%s: subkey id cannot be NULL"); 97 | ERRORIF (PG_ARGISNULL (2), "%s: subkey context cannot be NULL"); 98 | ERRORIF (PG_ARGISNULL (3), "%s: primary key cannot be NULL"); 99 | 100 | subkey_size = PG_GETARG_UINT32 (0); 101 | result_size = VARHDRSZ + subkey_size; 102 | subkey_id = PG_GETARG_INT64 (1); 103 | context = PG_GETARG_BYTEA_PP (2); 104 | primary_key = PG_GETARG_BYTEA_PP (3); 105 | 106 | ERRORIF (VARSIZE_ANY_EXHDR (primary_key) != crypto_kdf_KEYBYTES, 107 | "%s: invalid derivation key"); 108 | ERRORIF (subkey_size < crypto_kdf_BYTES_MIN || 109 | subkey_size > crypto_kdf_BYTES_MAX, "%s: invalid key size requested"); 110 | ERRORIF (VARSIZE_ANY_EXHDR (context) != 8, "%s: context must be 8 bytes"); 111 | result = _pgsodium_zalloc_bytea (result_size); 112 | crypto_kdf_derive_from_key ( 113 | PGSODIUM_UCHARDATA (result), 114 | subkey_size, 115 | subkey_id, 116 | (const char *) VARDATA_ANY (context), 117 | PGSODIUM_UCHARDATA_ANY (primary_key)); 118 | PG_RETURN_BYTEA_P (result); 119 | } 120 | -------------------------------------------------------------------------------- /src/pgsodium.c: -------------------------------------------------------------------------------- 1 | #include "pgsodium.h" 2 | 3 | #ifdef _WIN32 4 | #define X_OK 4 5 | #define access _access 6 | size_t getline(char **lineptr, size_t *n, FILE *stream) 7 | { 8 | // We expect 64 bytes, allocate enough to be sure. 9 | const int BUFFERSIZE = 1024; 10 | char *buffer = malloc (BUFFERSIZE); 11 | if (fgets (buffer, BUFFERSIZE - 1 , stream) == NULL) 12 | { 13 | free (buffer); 14 | return -1; 15 | } 16 | *lineptr = buffer; 17 | size_t char_read = strlen (*lineptr); 18 | *n = char_read; 19 | return char_read; 20 | } 21 | #endif 22 | 23 | PG_MODULE_MAGIC; 24 | 25 | bytea *pgsodium_secret_key; 26 | static char *getkey_script = NULL; 27 | static bool enable_event_trigger = true; 28 | 29 | /* 30 | * Checking the syntax of the masking rules 31 | */ 32 | static void 33 | pgsodium_object_relabel (const ObjectAddress * object, const char *seclabel) 34 | { 35 | /* SECURITY LABEL FOR pgsodium ON COLUMN foo.bar IS NULL */ 36 | if (seclabel == NULL) 37 | return; 38 | 39 | switch (object->classId) 40 | { 41 | case RelationRelationId: 42 | 43 | /* SECURITY LABEL FOR pgsodium ON TABLE ...' */ 44 | if (object->objectSubId == 0) 45 | { 46 | if (pg_strncasecmp (seclabel, "DECRYPT WITH VIEW", 17) == 0) 47 | return; 48 | ereport (ERROR, 49 | (errcode (ERRCODE_INVALID_NAME), 50 | errmsg ("'%s' is not a valid label for a table", 51 | seclabel))); 52 | } 53 | 54 | /* SECURITY LABEL FOR pgsodium ON COLUMN t.i IS '...' */ 55 | if (pg_strncasecmp (seclabel, "ENCRYPT WITH", 12) == 0) 56 | return; 57 | 58 | ereport (ERROR, 59 | (errcode (ERRCODE_INVALID_NAME), 60 | errmsg ("'%s' is not a valid label for a column", 61 | seclabel))); 62 | break; 63 | 64 | /* SECURITY LABEL FOR pgsodium ON ROLE sodium_user IS 'ACCESS' */ 65 | case AuthIdRelationId: 66 | if (pg_strncasecmp (seclabel, "ACCESS", 6) == 0) 67 | return; 68 | 69 | ereport (ERROR, 70 | (errcode (ERRCODE_INVALID_NAME), 71 | errmsg ("'%s' is not a valid label for a role", seclabel))); 72 | break; 73 | 74 | /* /\* SECURITY LABEL FOR pgsodium ON SCHEMA public IS 'TRUSTED' *\/ */ 75 | /* case NamespaceRelationId: */ 76 | /* if (!superuser()) */ 77 | /* ereport(ERROR, */ 78 | /* (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), */ 79 | /* errmsg("only superuser can set a pgsodium label for a schema"))); */ 80 | 81 | /* if (pg_strcasecmp(seclabel,"TRUSTED") == 0) */ 82 | /* return; */ 83 | 84 | /* ereport(ERROR, */ 85 | /* (errcode(ERRCODE_INVALID_NAME), */ 86 | /* errmsg("'%s' is not a valid label for a schema", seclabel))); */ 87 | /* break; */ 88 | 89 | /* everything else is unsupported */ 90 | default: 91 | ereport (ERROR, 92 | (errcode (ERRCODE_FEATURE_NOT_SUPPORTED), 93 | errmsg 94 | ("pgsodium provider does not support labels on this object"))); 95 | break; 96 | } 97 | 98 | ereport (ERROR, 99 | (errcode (ERRCODE_INVALID_NAME), 100 | errmsg ("'%s' is not a valid label", seclabel))); 101 | } 102 | 103 | 104 | void 105 | _PG_init (void) 106 | { 107 | FILE *fp; 108 | char *secret_buf = NULL; 109 | size_t secret_len = 0; 110 | size_t char_read; 111 | char *path; 112 | char sharepath[MAXPGPATH]; 113 | 114 | if (sodium_init () == -1) 115 | { 116 | elog (ERROR, 117 | "_PG_init: sodium_init() failed cannot initialize pgsodium"); 118 | return; 119 | } 120 | 121 | /* Security label provider hook */ 122 | register_label_provider ("pgsodium", pgsodium_object_relabel); 123 | 124 | // we're done if not preloaded 125 | if (!process_shared_preload_libraries_in_progress) 126 | return; 127 | 128 | // Variable to enable/disable event trigger 129 | DefineCustomBoolVariable("pgsodium.enable_event_trigger", 130 | "Variable to enable/disable event trigger that regenerates triggers and views.", 131 | NULL, 132 | &enable_event_trigger, 133 | true, 134 | PGC_USERSET, 0, 135 | NULL, NULL, NULL); 136 | 137 | // try to get internal shared key 138 | path = (char *) palloc0 (MAXPGPATH); 139 | get_share_path (my_exec_path, sharepath); 140 | snprintf (path, MAXPGPATH, "%s/extension/%s", sharepath, PG_GETKEY_EXEC); 141 | 142 | DefineCustomStringVariable ("pgsodium.getkey_script", 143 | "path to script that returns pgsodium root key", 144 | NULL, &getkey_script, path, PGC_POSTMASTER, 0, NULL, NULL, NULL); 145 | 146 | if (access (getkey_script, X_OK) == -1) 147 | { 148 | if (errno == ENOENT) 149 | ereport(ERROR, ( 150 | errmsg("The getkey script \"%s\" does not exists.", getkey_script), 151 | errdetail("The getkey script fetches the primary server secret key."), 152 | errhint("You might want to create it and/or set \"pgsodium.getkey_script\" to the correct path."))); 153 | else if (errno == EACCES) 154 | ereport(ERROR, 155 | errmsg("Permission denied for the getkey script \"%s\"", 156 | getkey_script)); 157 | else 158 | ereport(ERROR, 159 | errmsg("Can not access getkey script \"%s\"", getkey_script)); 160 | proc_exit (1); 161 | } 162 | 163 | if ((fp = popen (getkey_script, "r")) == NULL) 164 | { 165 | ereport(ERROR, 166 | errmsg("%s: could not launch shell command from", getkey_script)); 167 | proc_exit (1); 168 | } 169 | 170 | if ((char_read = getline (&secret_buf, &secret_len, fp)) == -1) 171 | { 172 | ereport(ERROR, errmsg("unable to read secret key")); 173 | proc_exit (1); 174 | } 175 | 176 | if (secret_buf[char_read - 1] == '\n') 177 | secret_buf[char_read - 1] = '\0'; 178 | 179 | secret_len = strlen (secret_buf); 180 | 181 | if (secret_len != 64) 182 | { 183 | ereport(ERROR, errmsg("invalid secret key")); 184 | proc_exit (1); 185 | } 186 | 187 | if (pclose (fp) != 0) 188 | { 189 | ereport(ERROR, errmsg( "%s: could not close shell command\n", 190 | PG_GETKEY_EXEC)); 191 | proc_exit (1); 192 | } 193 | pgsodium_secret_key = 194 | sodium_malloc (crypto_sign_SECRETKEYBYTES + VARHDRSZ); 195 | 196 | if (pgsodium_secret_key == NULL) 197 | { 198 | ereport(ERROR, errmsg( "%s: sodium_malloc() failed\n", PG_GETKEY_EXEC)); 199 | proc_exit (1); 200 | } 201 | 202 | hex_decode (secret_buf, secret_len, VARDATA (pgsodium_secret_key)); 203 | sodium_memzero (secret_buf, secret_len); 204 | free (secret_buf); 205 | elog (LOG, "pgsodium primary server secret key loaded"); 206 | } 207 | -------------------------------------------------------------------------------- /src/pwhash.c: -------------------------------------------------------------------------------- 1 | /* doctest/pwhash 2 | -- # Password Hashing 3 | -- 4 | -- Secret keys used to encrypt or sign confidential data have to be 5 | -- chosen from a very large keyspace. 6 | -- 7 | -- However, passwords are usually short, human-generated strings, making 8 | -- dictionary attacks practical. 9 | -- 10 | -- Password hashing functions derive a secret key of any size from a 11 | -- password and salt. 12 | -- 13 | -- - The generated key has the size defined by the application, no 14 | -- matter what the password length is. 15 | -- 16 | -- - The same password hashed with the same parameters will always 17 | -- produce the same output. 18 | -- 19 | -- - The same password hashed with different salts will produce 20 | -- different outputs. 21 | -- 22 | -- - The function deriving a key from a password and salt is CPU 23 | -- intensive and intentionally requires a fair amount of 24 | -- memory. Therefore, it mitigates brute-force attacks by requiring a 25 | -- significant effort to verify each password. 26 | -- 27 | -- Common use cases: 28 | -- 29 | -- - Password storage, or rather storing what it takes to verify a 30 | -- password without having to store the actual password. 31 | -- 32 | -- - Deriving a secret key from a password; for example, for disk 33 | -- encryption. 34 | -- 35 | -- Sodium's high-level crypto_pwhash_* API currently leverages the 36 | -- Argon2id function on all platforms. This can change at any point in 37 | -- time, but it is guaranteed that a given version of libsodium can 38 | -- verify all hashes produced by all previous versions from any 39 | -- platform. Applications don't have to worry about backward 40 | -- compatibility. 41 | 42 | select pgsodium.crypto_pwhash_saltgen() salt \gset 43 | 44 | select pgsodium.crypto_pwhash('Correct Horse Battery Staple', :'salt'); 45 | 46 | select pgsodium.crypto_pwhash_str('Correct Horse Battery Staple') hash \gset 47 | 48 | select pgsodium.crypto_pwhash_str_verify((:'hash')::bytea, 'Correct Horse Battery Staple'); 49 | 50 | */ 51 | 52 | #include "pgsodium.h" 53 | 54 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_pwhash_saltgen); 55 | Datum 56 | pgsodium_crypto_pwhash_saltgen (PG_FUNCTION_ARGS) 57 | { 58 | size_t result_size = VARHDRSZ + crypto_pwhash_SALTBYTES; 59 | bytea *result = _pgsodium_zalloc_bytea (result_size); 60 | randombytes_buf (VARDATA (result), crypto_pwhash_SALTBYTES); 61 | PG_RETURN_BYTEA_P (result); 62 | } 63 | 64 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_pwhash); 65 | Datum 66 | pgsodium_crypto_pwhash (PG_FUNCTION_ARGS) 67 | { 68 | bytea *data; 69 | bytea *result; 70 | bytea *salt; 71 | int result_size = VARHDRSZ + crypto_box_SEEDBYTES; 72 | int success; 73 | 74 | ERRORIF (PG_ARGISNULL (0), "%s: data cannot be NULL"); 75 | ERRORIF (PG_ARGISNULL (1), "%s: salt cannot be NULL"); 76 | 77 | data = PG_GETARG_BYTEA_PP (0); 78 | salt = PG_GETARG_BYTEA_PP (1); 79 | 80 | ERRORIF (VARSIZE_ANY_EXHDR (salt) != crypto_pwhash_SALTBYTES, 81 | "%s: invalid salt"); 82 | ERRORIF (VARSIZE_ANY_EXHDR (data) < crypto_pwhash_PASSWD_MIN 83 | || VARSIZE_ANY_EXHDR (data) > crypto_pwhash_PASSWD_MAX, 84 | "%s: invalid password"); 85 | result = _pgsodium_zalloc_bytea (result_size); 86 | success = crypto_pwhash ( 87 | PGSODIUM_UCHARDATA (result), 88 | crypto_box_SEEDBYTES, 89 | VARDATA_ANY (data), 90 | VARSIZE_ANY_EXHDR (data), 91 | PGSODIUM_UCHARDATA_ANY (salt), 92 | crypto_pwhash_OPSLIMIT_MODERATE, 93 | crypto_pwhash_MEMLIMIT_MODERATE, crypto_pwhash_ALG_DEFAULT); 94 | ERRORIF (success != 0, "%s: invalid message"); 95 | PG_RETURN_BYTEA_P (result); 96 | } 97 | 98 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_pwhash_str); 99 | Datum 100 | pgsodium_crypto_pwhash_str (PG_FUNCTION_ARGS) 101 | { 102 | int success; 103 | bytea *password; 104 | bytea *result = 105 | _pgsodium_zalloc_bytea (crypto_pwhash_STRBYTES + VARHDRSZ); 106 | 107 | ERRORIF (PG_ARGISNULL (0), "%s: password cannot be NULL"); 108 | 109 | password = PG_GETARG_BYTEA_PP (0); 110 | success = 111 | crypto_pwhash_str ( 112 | VARDATA (result), 113 | VARDATA_ANY (password), 114 | VARSIZE_ANY_EXHDR (password), 115 | crypto_pwhash_OPSLIMIT_MODERATE, 116 | crypto_pwhash_MEMLIMIT_MODERATE); 117 | ERRORIF (success != 0, "%s: out of memory in pwhash_str"); 118 | PG_RETURN_BYTEA_P (result); 119 | } 120 | 121 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_pwhash_str_verify); 122 | Datum 123 | pgsodium_crypto_pwhash_str_verify (PG_FUNCTION_ARGS) 124 | { 125 | int success; 126 | bytea *hashed_password; 127 | bytea *password; 128 | 129 | ERRORIF (PG_ARGISNULL (0), "%s: hashed password cannot be NULL"); 130 | ERRORIF (PG_ARGISNULL (1), "%s: password cannot be NULL"); 131 | 132 | hashed_password = PG_GETARG_BYTEA_PP (0); 133 | password = PG_GETARG_BYTEA_PP (1); 134 | 135 | success = crypto_pwhash_str_verify ( 136 | VARDATA_ANY (hashed_password), 137 | VARDATA_ANY (password), 138 | VARSIZE_ANY_EXHDR (password)); 139 | PG_RETURN_BOOL (success == 0); 140 | } 141 | -------------------------------------------------------------------------------- /src/random.c: -------------------------------------------------------------------------------- 1 | /* doctest/random 2 | -- # Generating Random Data 3 | -- 4 | \pset linestyle unicode 5 | \pset border 2 6 | \pset pager off 7 | create extension if not exists pgsodium; -- pragma:hide 8 | set search_path to pgsodium,public; -- pragma:hide 9 | -- 10 | -- The library provides a set of functions to generate unpredictable data, suitable for creating secret keys. 11 | -- 12 | -- - On Windows systems, the RtlGenRandom() function is used. 13 | -- - On OpenBSD and Bitrig, the arc4random() function is used. 14 | -- - On recent FreeBSD and Linux kernels, the getrandom system call is used. 15 | -- - On other Unices, the /dev/urandom device is used. 16 | 17 | -- ## `randombytes_random()` 18 | -- 19 | -- Returns a random 32-bit signed integer. 20 | 21 | select pgsodium.randombytes_random() from generate_series(0, 5); 22 | 23 | -- ## `randombytes_uniform(upper_bound interger)` 24 | -- 25 | -- Returns a uniformally distributed random number between zero and the upper bound argument. 26 | 27 | select pgsodium.randombytes_uniform(10) + 3 from generate_series(0, 5); 28 | 29 | -- ## `randombytes_buf(buffer_size integer)` 30 | -- 31 | -- Returns a random buffer of bytes the size of the argument. 32 | 33 | select encode(pgsodium.randombytes_buf(10), 'hex') from generate_series(0, 5); 34 | 35 | */ 36 | 37 | #include "pgsodium.h" 38 | 39 | PG_FUNCTION_INFO_V1 (pgsodium_randombytes_random); 40 | Datum 41 | pgsodium_randombytes_random (PG_FUNCTION_ARGS) 42 | { 43 | PG_RETURN_UINT32 (randombytes_random ()); 44 | } 45 | 46 | PG_FUNCTION_INFO_V1 (pgsodium_randombytes_uniform); 47 | Datum 48 | pgsodium_randombytes_uniform (PG_FUNCTION_ARGS) 49 | { 50 | uint32_t upper_bound; 51 | ERRORIF (PG_ARGISNULL (0), "%s: upper bound cannot be NULL"); 52 | upper_bound = PG_GETARG_UINT32 (0); 53 | PG_RETURN_UINT32 (randombytes_uniform (upper_bound)); 54 | } 55 | 56 | PG_FUNCTION_INFO_V1 (pgsodium_randombytes_buf); 57 | Datum 58 | pgsodium_randombytes_buf (PG_FUNCTION_ARGS) 59 | { 60 | size_t size; 61 | size_t result_size; 62 | bytea *result; 63 | 64 | ERRORIF (PG_ARGISNULL (0), "%s: buffer size cannot be NULL"); 65 | size = PG_GETARG_UINT32 (0); 66 | result_size = VARHDRSZ + size; 67 | result = _pgsodium_zalloc_bytea (result_size); 68 | 69 | randombytes_buf (VARDATA (result), size); 70 | PG_RETURN_BYTEA_P (result); 71 | } 72 | 73 | PGDLLEXPORT PG_FUNCTION_INFO_V1 (pgsodium_randombytes_new_seed); 74 | Datum 75 | pgsodium_randombytes_new_seed (PG_FUNCTION_ARGS) 76 | { 77 | size_t result_size = VARHDRSZ + randombytes_SEEDBYTES; 78 | bytea *result = _pgsodium_zalloc_bytea (result_size); 79 | randombytes_buf (VARDATA (result), randombytes_SEEDBYTES); 80 | PG_RETURN_BYTEA_P (result); 81 | } 82 | 83 | PG_FUNCTION_INFO_V1 (pgsodium_randombytes_buf_deterministic); 84 | Datum 85 | pgsodium_randombytes_buf_deterministic (PG_FUNCTION_ARGS) 86 | { 87 | size_t size; 88 | bytea *seed; 89 | size_t result_size; 90 | bytea *result; 91 | 92 | ERRORIF (PG_ARGISNULL (0), "%s: buffer size cannot be NULL"); 93 | ERRORIF (PG_ARGISNULL (1), "%s: seed cannot be NULL"); 94 | 95 | size = PG_GETARG_UINT32 (0); 96 | seed = PG_GETARG_BYTEA_P (1); 97 | result_size = VARHDRSZ + size; 98 | result = _pgsodium_zalloc_bytea (result_size); 99 | 100 | randombytes_buf_deterministic (VARDATA (result), size, 101 | PGSODIUM_UCHARDATA (seed)); 102 | PG_RETURN_BYTEA_P (result); 103 | } 104 | -------------------------------------------------------------------------------- /src/secretbox.c: -------------------------------------------------------------------------------- 1 | /* doctest/secretbox 2 | \pset linestyle unicode 3 | \pset border 2 4 | \pset pager off 5 | create extension if not exists pgsodium; -- pragma:hide 6 | set search_path to pgsodium,public; -- pragma:hide 7 | -- # Secret Key Cryptography 8 | 9 | select pgsodium.crypto_secretbox_keygen() key \gset 10 | 11 | select pgsodium.crypto_secretbox_noncegen() nonce \gset 12 | 13 | select pgsodium.crypto_secretbox('bob is your uncle', :'nonce', :'key') secretbox \gset 14 | 15 | select pgsodium.crypto_secretbox_open(:'secretbox', :'nonce', :'key'); 16 | 17 | */ 18 | #include "pgsodium.h" 19 | 20 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_secretbox_keygen); 21 | Datum 22 | pgsodium_crypto_secretbox_keygen (PG_FUNCTION_ARGS) 23 | { 24 | size_t result_size = VARHDRSZ + crypto_secretbox_KEYBYTES; 25 | bytea *result = _pgsodium_zalloc_bytea (result_size); 26 | crypto_secretbox_keygen (PGSODIUM_UCHARDATA (result)); 27 | PG_RETURN_BYTEA_P (result); 28 | } 29 | 30 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_secretbox_noncegen); 31 | Datum 32 | pgsodium_crypto_secretbox_noncegen (PG_FUNCTION_ARGS) 33 | { 34 | int result_size = VARHDRSZ + crypto_secretbox_NONCEBYTES; 35 | bytea *result = _pgsodium_zalloc_bytea (result_size); 36 | randombytes_buf (VARDATA (result), crypto_secretbox_NONCEBYTES); 37 | PG_RETURN_BYTEA_P (result); 38 | } 39 | 40 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_secretbox); 41 | Datum 42 | pgsodium_crypto_secretbox (PG_FUNCTION_ARGS) 43 | { 44 | bytea *message; 45 | bytea *nonce; 46 | bytea *key; 47 | size_t result_size; 48 | bytea *result; 49 | 50 | ERRORIF (PG_ARGISNULL (0), "%s: message cannot be NULL"); 51 | ERRORIF (PG_ARGISNULL (1), "%s: nonce cannot be NULL"); 52 | ERRORIF (PG_ARGISNULL (2), "%s: key cannot be NULL"); 53 | 54 | message = PG_GETARG_BYTEA_P (0); 55 | nonce = PG_GETARG_BYTEA_P (1); 56 | key = PG_GETARG_BYTEA_P (2); 57 | 58 | ERRORIF (VARSIZE_ANY_EXHDR (nonce) != crypto_secretbox_NONCEBYTES, 59 | "%s: invalid nonce"); 60 | ERRORIF (VARSIZE_ANY_EXHDR (key) != crypto_secretbox_KEYBYTES, 61 | "%s: invalid key"); 62 | result_size = crypto_secretbox_MACBYTES + VARSIZE_ANY (message); 63 | result = _pgsodium_zalloc_bytea (result_size); 64 | crypto_secretbox_easy (PGSODIUM_UCHARDATA (result), 65 | PGSODIUM_UCHARDATA (message), 66 | VARSIZE_ANY_EXHDR (message), 67 | PGSODIUM_UCHARDATA (nonce), PGSODIUM_UCHARDATA (key)); 68 | PG_RETURN_BYTEA_P (result); 69 | } 70 | 71 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_secretbox_by_id); 72 | Datum 73 | pgsodium_crypto_secretbox_by_id (PG_FUNCTION_ARGS) 74 | { 75 | bytea *message; 76 | bytea *nonce; 77 | unsigned long long key_id; 78 | bytea *context; 79 | bytea *key; 80 | size_t result_size; 81 | bytea *result; 82 | 83 | ERRORIF (PG_ARGISNULL (0), "%s: message cannot be NULL"); 84 | ERRORIF (PG_ARGISNULL (1), "%s: nonce cannot be NULL"); 85 | ERRORIF (PG_ARGISNULL (2), "%s: key id cannot be NULL"); 86 | ERRORIF (PG_ARGISNULL (3), "%s: key context cannot be NULL"); 87 | 88 | message = PG_GETARG_BYTEA_P (0); 89 | nonce = PG_GETARG_BYTEA_P (1); 90 | key_id = PG_GETARG_INT64 (2); 91 | context = PG_GETARG_BYTEA_P (3); 92 | 93 | key = pgsodium_derive_helper (key_id, crypto_secretbox_KEYBYTES, context); 94 | result_size = crypto_secretbox_MACBYTES + VARSIZE_ANY (message); 95 | result = _pgsodium_zalloc_bytea (result_size); 96 | 97 | crypto_secretbox_easy (PGSODIUM_UCHARDATA (result), 98 | PGSODIUM_UCHARDATA (message), 99 | VARSIZE_ANY_EXHDR (message), 100 | PGSODIUM_UCHARDATA (nonce), PGSODIUM_UCHARDATA (key)); 101 | PG_RETURN_BYTEA_P (result); 102 | } 103 | 104 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_secretbox_open); 105 | Datum 106 | pgsodium_crypto_secretbox_open (PG_FUNCTION_ARGS) 107 | { 108 | int success; 109 | bytea *message; 110 | bytea *nonce; 111 | bytea *key; 112 | size_t message_size; 113 | size_t result_size; 114 | bytea *result; 115 | 116 | ERRORIF (PG_ARGISNULL (0), "%s: message cannot be NULL"); 117 | ERRORIF (PG_ARGISNULL (1), "%s: nonce cannot be NULL"); 118 | ERRORIF (PG_ARGISNULL (2), "%s: key cannot be NULL"); 119 | 120 | message = PG_GETARG_BYTEA_P (0); 121 | nonce = PG_GETARG_BYTEA_P (1); 122 | key = PG_GETARG_BYTEA_P (2); 123 | 124 | ERRORIF (VARSIZE_ANY_EXHDR (message) <= crypto_secretbox_MACBYTES, 125 | "%s: invalid message"); 126 | ERRORIF (VARSIZE_ANY_EXHDR (nonce) != crypto_secretbox_NONCEBYTES, 127 | "%s: invalid nonce"); 128 | ERRORIF (VARSIZE_ANY_EXHDR (key) != crypto_secretbox_KEYBYTES, 129 | "%s: invalid key"); 130 | 131 | message_size = VARSIZE_ANY_EXHDR (message) - crypto_secretbox_MACBYTES; 132 | result_size = VARHDRSZ + message_size; 133 | result = _pgsodium_zalloc_bytea (result_size); 134 | 135 | success = crypto_secretbox_open_easy (PGSODIUM_UCHARDATA (result), 136 | PGSODIUM_UCHARDATA (message), 137 | VARSIZE_ANY_EXHDR (message), 138 | PGSODIUM_UCHARDATA (nonce), PGSODIUM_UCHARDATA (key)); 139 | ERRORIF (success != 0, "%s: invalid message"); 140 | PG_RETURN_BYTEA_P (result); 141 | } 142 | 143 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_secretbox_open_by_id); 144 | Datum 145 | pgsodium_crypto_secretbox_open_by_id (PG_FUNCTION_ARGS) 146 | { 147 | int success; 148 | bytea *message; 149 | bytea *nonce; 150 | unsigned long long key_id; 151 | bytea *context; 152 | bytea *key; 153 | size_t message_size; 154 | size_t result_size; 155 | bytea *result; 156 | 157 | ERRORIF (PG_ARGISNULL (0), "%s: message cannot be NULL"); 158 | ERRORIF (PG_ARGISNULL (1), "%s: nonce cannot be NULL"); 159 | ERRORIF (PG_ARGISNULL (2), "%s: key id cannot be NULL"); 160 | ERRORIF (PG_ARGISNULL (3), "%s: key context cannot be NULL"); 161 | 162 | message = PG_GETARG_BYTEA_P (0); 163 | nonce = PG_GETARG_BYTEA_P (1); 164 | key_id = PG_GETARG_INT64 (2); 165 | context = PG_GETARG_BYTEA_P (3); 166 | key = pgsodium_derive_helper (key_id, crypto_secretbox_KEYBYTES, context); 167 | 168 | ERRORIF (VARSIZE_ANY_EXHDR (message) <= crypto_secretbox_MACBYTES, 169 | "%s: invalid message"); 170 | ERRORIF (VARSIZE_ANY_EXHDR (nonce) != crypto_secretbox_NONCEBYTES, 171 | "%s: invalid nonce"); 172 | 173 | message_size = VARSIZE_ANY_EXHDR (message) - crypto_secretbox_MACBYTES; 174 | result_size = VARHDRSZ + message_size; 175 | result = _pgsodium_zalloc_bytea (result_size); 176 | 177 | success = crypto_secretbox_open_easy (PGSODIUM_UCHARDATA (result), 178 | PGSODIUM_UCHARDATA (message), 179 | VARSIZE_ANY_EXHDR (message), 180 | PGSODIUM_UCHARDATA (nonce), PGSODIUM_UCHARDATA (key)); 181 | ERRORIF (success != 0, "%s: invalid message"); 182 | PG_RETURN_BYTEA_P (result); 183 | } 184 | -------------------------------------------------------------------------------- /src/secretstream.c: -------------------------------------------------------------------------------- 1 | 2 | #include "pgsodium.h" 3 | 4 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_secretstream_xchacha20poly1305_keygen); 5 | Datum 6 | pgsodium_crypto_secretstream_xchacha20poly1305_keygen (PG_FUNCTION_ARGS) 7 | { 8 | size_t result_size = 9 | VARHDRSZ + crypto_secretstream_xchacha20poly1305_KEYBYTES; 10 | bytea *result = _pgsodium_zalloc_bytea (result_size); 11 | crypto_secretstream_xchacha20poly1305_keygen (PGSODIUM_UCHARDATA (result)); 12 | PG_RETURN_BYTEA_P (result); 13 | } 14 | -------------------------------------------------------------------------------- /src/sha.c: -------------------------------------------------------------------------------- 1 | #include "pgsodium.h" 2 | 3 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_hash_sha256); 4 | Datum 5 | pgsodium_crypto_hash_sha256 (PG_FUNCTION_ARGS) 6 | { 7 | size_t result_size = VARHDRSZ + crypto_hash_sha256_BYTES; 8 | bytea *message; 9 | bytea *result; 10 | 11 | ERRORIF (PG_ARGISNULL (0), "%s: message cannot be NULL"); 12 | 13 | message = PG_GETARG_BYTEA_PP (0); 14 | result = _pgsodium_zalloc_bytea (result_size); 15 | crypto_hash_sha256 ( 16 | PGSODIUM_UCHARDATA (result), 17 | PGSODIUM_UCHARDATA_ANY (message), 18 | VARSIZE_ANY_EXHDR (message)); 19 | PG_RETURN_BYTEA_P (result); 20 | } 21 | 22 | PG_FUNCTION_INFO_V1 (pgsodium_crypto_hash_sha512); 23 | Datum 24 | pgsodium_crypto_hash_sha512 (PG_FUNCTION_ARGS) 25 | { 26 | size_t result_size = VARHDRSZ + crypto_hash_sha512_BYTES; 27 | bytea *message; 28 | bytea *result; 29 | 30 | ERRORIF (PG_ARGISNULL (0), "%s: message cannot be NULL"); 31 | 32 | message = PG_GETARG_BYTEA_PP (0); 33 | result = _pgsodium_zalloc_bytea (result_size); 34 | 35 | crypto_hash_sha512 ( 36 | PGSODIUM_UCHARDATA (result), 37 | PGSODIUM_UCHARDATA_ANY (message), 38 | VARSIZE_ANY_EXHDR (message)); 39 | PG_RETURN_BYTEA_P (result); 40 | } 41 | -------------------------------------------------------------------------------- /src/signcrypt_tbsbr.h: -------------------------------------------------------------------------------- 1 | /* MIT License */ 2 | 3 | /* Copyright (c) 2020-2021 Frank Denis */ 4 | 5 | /* Permission is hereby granted, free of charge, to any person obtaining a copy */ 6 | /* of this software and associated documentation files (the "Software"), to deal */ 7 | /* in the Software without restriction, including without limitation the rights */ 8 | /* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell */ 9 | /* copies of the Software, and to permit persons to whom the Software is */ 10 | /* furnished to do so, subject to the following conditions: */ 11 | 12 | /* The above copyright notice and this permission notice shall be included in all */ 13 | /* copies or substantial portions of the Software. */ 14 | 15 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR */ 16 | /* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, */ 17 | /* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE */ 18 | /* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER */ 19 | /* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, */ 20 | /* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE */ 21 | /* SOFTWARE. */ 22 | 23 | #ifndef signcrypt_tbsbr_H 24 | #define signcrypt_tbsbr_H 25 | 26 | #ifdef __cplusplus 27 | extern "C" 28 | { 29 | #endif 30 | 31 | #include 32 | 33 | #if !defined(__clang__) && !defined(__GNUC__) && !defined(__attribute__) 34 | #define __attribute__(X) 35 | #endif 36 | 37 | #define crypto_signcrypt_tbsbr_SECRETKEYBYTES 32 38 | #define crypto_signcrypt_tbsbr_PUBLICKEYBYTES 32 39 | #define crypto_signcrypt_tbsbr_SHAREDBYTES 32 40 | #define crypto_signcrypt_tbsbr_SEEDBYTES 64 41 | #define crypto_signcrypt_tbsbr_SIGNBYTES (32 + 32) 42 | #define crypto_signcrypt_tbsbr_STATEBYTES 512 43 | 44 | int crypto_signcrypt_tbsbr_sign_before (unsigned char 45 | st[crypto_signcrypt_tbsbr_STATEBYTES], 46 | unsigned char shared_key[crypto_signcrypt_tbsbr_SHAREDBYTES], 47 | const unsigned char *sender_id, size_t sender_id_len, 48 | const unsigned char *recipient_id, size_t recipient_id_len, 49 | const unsigned char *info, size_t info_len, 50 | const unsigned char sender_sk[crypto_signcrypt_tbsbr_SECRETKEYBYTES], 51 | const unsigned char 52 | recipient_pk[crypto_signcrypt_tbsbr_PUBLICKEYBYTES], 53 | const unsigned char *m, size_t m_len) 54 | __attribute__((warn_unused_result)); 55 | 56 | int crypto_signcrypt_tbsbr_sign_after (unsigned char 57 | st[crypto_signcrypt_tbsbr_STATEBYTES], 58 | unsigned char sig[crypto_signcrypt_tbsbr_SIGNBYTES], 59 | const unsigned char sender_sk[crypto_signcrypt_tbsbr_SECRETKEYBYTES], 60 | const unsigned char *c, size_t c_len) 61 | __attribute__((warn_unused_result)); 62 | 63 | int crypto_signcrypt_tbsbr_verify_before (unsigned char 64 | st[crypto_signcrypt_tbsbr_STATEBYTES], 65 | unsigned char shared_key[crypto_signcrypt_tbsbr_SHAREDBYTES], 66 | const unsigned char sig[crypto_signcrypt_tbsbr_SIGNBYTES], 67 | const unsigned char *sender_id, size_t sender_id_len, 68 | const unsigned char *recipient_id, size_t recipient_id_len, 69 | const unsigned char *info, size_t info_len, 70 | const unsigned char sender_pk[crypto_signcrypt_tbsbr_PUBLICKEYBYTES], 71 | const unsigned char 72 | recipient_sk[crypto_signcrypt_tbsbr_PUBLICKEYBYTES]) 73 | __attribute__((warn_unused_result)); 74 | 75 | int crypto_signcrypt_tbsbr_verify_after (unsigned char 76 | st[crypto_signcrypt_tbsbr_STATEBYTES], 77 | const unsigned char sig[crypto_signcrypt_tbsbr_SIGNBYTES], 78 | const unsigned char sender_pk[crypto_signcrypt_tbsbr_PUBLICKEYBYTES], 79 | const unsigned char *c, size_t c_len) 80 | __attribute__((warn_unused_result)); 81 | 82 | int crypto_signcrypt_tbsr_verify_public (const unsigned char 83 | sig[crypto_signcrypt_tbsbr_SIGNBYTES], const unsigned char *sender_id, 84 | size_t sender_id_len, const unsigned char *recipient_id, 85 | size_t recipient_id_len, const unsigned char *info, size_t info_len, 86 | const unsigned char sender_pk[crypto_signcrypt_tbsbr_PUBLICKEYBYTES], 87 | const unsigned char *c, size_t c_len) 88 | __attribute__((warn_unused_result)); 89 | 90 | void crypto_signcrypt_tbsbr_keygen (unsigned char 91 | pk[crypto_signcrypt_tbsbr_PUBLICKEYBYTES], 92 | unsigned char sk[crypto_signcrypt_tbsbr_SECRETKEYBYTES]); 93 | 94 | void crypto_signcrypt_tbsbr_seed_keygen (unsigned char 95 | pk[crypto_signcrypt_tbsbr_PUBLICKEYBYTES], 96 | unsigned char sk[crypto_signcrypt_tbsbr_SECRETKEYBYTES], 97 | const unsigned char seed[crypto_signcrypt_tbsbr_SEEDBYTES]); 98 | 99 | #ifdef __cplusplus 100 | } 101 | #endif 102 | 103 | #endif 104 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | versions=${1:-13 14 15 16} 6 | 7 | for version in $versions 8 | do 9 | for config in '-c shared_preload_libraries=pgsodium' '-c shared_preload_libraries=pgsodium -c pgsodium.getkey_script=/getkey' '' 10 | do 11 | DB_HOST="pgsodium-test-db-$version" 12 | DB_NAME="postgres" 13 | SU="postgres" 14 | EXEC="docker exec -i $DB_HOST" 15 | TAG="pgsodium/test-$version" 16 | 17 | echo building test image $DB_HOST 18 | docker build . -t $TAG --build-arg "version=$version" 19 | 20 | echo running test container 21 | docker run --rm -e POSTGRES_HOST_AUTH_METHOD=trust -d \ 22 | -v `pwd`/test:/home/postgres/pgsodium/test:Z \ 23 | -v `pwd`/example:/home/postgres/pgsodium/example:Z \ 24 | --name "$DB_HOST" $TAG postgres $config 25 | 26 | echo waiting for database to accept connections 27 | sleep 3; 28 | echo running tests 29 | 30 | $EXEC pg_prove -U "$SU" /home/postgres/pgsodium/test/test.sql 31 | 32 | echo destroying test container and image 33 | docker rm --force "$DB_HOST" 34 | done 35 | done 36 | -------------------------------------------------------------------------------- /test/auth.sql: -------------------------------------------------------------------------------- 1 | 2 | SELECT crypto_auth_keygen() authkey \gset 3 | 4 | SELECT crypto_auth('bob is your uncle', :'authkey'::bytea) auth_mac \gset 5 | 6 | SELECT throws_ok($$select crypto_auth('bob is your uncle', 'bad_key'::bytea)$$, 7 | '22000', 'pgsodium_crypto_auth: invalid key', 'crypto_auth invalid key'); 8 | 9 | SELECT throws_ok($$select crypto_auth(NULL, 'bad_key'::bytea)$$, 10 | '22000', 'pgsodium_crypto_auth: message cannot be NULL', 'crypto_auth null message'); 11 | 12 | SELECT throws_ok($$select crypto_auth('bob is your uncle', NULL::bytea)$$, 13 | '22000', 'pgsodium_crypto_auth: key cannot be NULL', 'crypto_auth null key'); 14 | 15 | SELECT ok(crypto_auth_verify(:'auth_mac', 'bob is your uncle', :'authkey'::bytea), 16 | 'crypto_auth_verify'); 17 | 18 | SELECT throws_ok(format($$select crypto_auth_verify('bad mac', 'bob is your uncle', %L::bytea)$$, :'authkey'), 19 | '22000', 'pgsodium_crypto_auth_verify: invalid mac', 'crypto_auth_verify invalid mac'); 20 | 21 | SELECT throws_ok(format($$select crypto_auth_verify(%L, 'bob is your uncle', 'bad_key'::bytea)$$, :'auth_mac'), 22 | '22000', 'pgsodium_crypto_auth_verify: invalid key', 'crypto_auth_verify invalid key'); 23 | 24 | SELECT throws_ok($$select crypto_auth_verify(NULL, 'bob is your uncle', 'bad_key'::bytea)$$, 25 | '22000', 'pgsodium_crypto_auth_verify: signature cannot be NULL', 'crypto_auth_verify null signature'); 26 | 27 | SELECT throws_ok($$select crypto_auth_verify('sig', NULL, 'bad_key'::bytea)$$, 28 | '22000', 'pgsodium_crypto_auth_verify: message cannot be NULL', 'crypto_auth_verify null message'); 29 | 30 | SELECT throws_ok($$select crypto_auth_verify('sig', 'bob is your uncle', NULL::bytea)$$, 31 | '22000', 'pgsodium_crypto_auth_verify: key cannot be NULL', 'crypto_auth_verify null key'); 32 | 33 | 34 | \if :serverkeys 35 | 36 | SELECT crypto_auth('bob is your uncle', 1) auth_mac_by_id \gset 37 | 38 | SELECT throws_ok($$select crypto_auth(NULL, 1::bigint)$$, 39 | '22000', 'pgsodium_crypto_auth_by_id: message cannot be NULL', 'crypto_auth null message'); 40 | 41 | SELECT throws_ok($$select crypto_auth('bob is your uncle', NULL::bigint)$$, 42 | '22000', 'pgsodium_crypto_auth_by_id: key id cannot be NULL', 'crypto_auth null key id'); 43 | 44 | SELECT throws_ok($$select crypto_auth('bob is your uncle', 1, NULL::bytea)$$, 45 | '22000', 'pgsodium_crypto_auth_by_id: key context cannot be NULL', 'crypto_auth null context'); 46 | 47 | SELECT ok(crypto_auth_verify(:'auth_mac_by_id', 'bob is your uncle', 1), 48 | 'crypto_auth_verify by id'); 49 | 50 | SELECT id AS auth_key_id FROM create_key('auth') auth_key_id \gset 51 | 52 | SELECT crypto_auth('bobo is your monkey', :'auth_key_id'::uuid) auth_mac_by_uuid \gset 53 | SELECT ok(crypto_auth_verify(:'auth_mac_by_uuid', 'bobo is your monkey', :'auth_key_id'::uuid), 54 | 'crypto_auth_verify by uuid'); 55 | 56 | \endif 57 | -------------------------------------------------------------------------------- /test/box.sql: -------------------------------------------------------------------------------- 1 | 2 | SELECT crypto_box_noncegen() boxnonce \gset 3 | select crypto_box_new_seed() boxseed \gset 4 | SELECT public, secret FROM crypto_box_new_keypair() \gset bob_ 5 | SELECT public, secret FROM crypto_box_seed_new_keypair(:'boxseed') \gset alice_ 6 | 7 | select throws_ok($$select crypto_box_seed_new_keypair(NULL)$$, '22000', 8 | 'pgsodium_crypto_box_seed_keypair: seed cannot be NULL', 'crypto_box_seed_new_keypair null seed'); 9 | 10 | SELECT crypto_box('bob is your uncle', :'boxnonce', :'bob_public', :'alice_secret') box \gset 11 | 12 | SELECT throws_ok($$select crypto_box(NULL, 'bad', 'bad', 'bad')$$, 13 | '22000', 'pgsodium_crypto_box: message cannot be NULL', 'crypto_box null message'); 14 | 15 | SELECT throws_ok($$select crypto_box('bad', NULL, 'bad', 'bad')$$, 16 | '22000', 'pgsodium_crypto_box: nonce cannot be NULL', 'crypto_box null nonce'); 17 | 18 | SELECT throws_ok($$select crypto_box('bad', 'bad', NULL, 'bad')$$, 19 | '22000', 'pgsodium_crypto_box: publickey cannot be NULL', 'crypto_box null publickey'); 20 | 21 | SELECT throws_ok($$select crypto_box('bad', 'bad', 'bad', NULL)$$, 22 | '22000', 'pgsodium_crypto_box: secretkey cannot be NULL', 'crypto_box null secretkey'); 23 | 24 | SELECT is(crypto_box_open(:'box', :'boxnonce', :'alice_public', :'bob_secret'), 25 | 'bob is your uncle', 'crypto_box_open'); 26 | 27 | SELECT throws_ok(format($$select crypto_box_open(%L, 'bad nonce', %L, %L)$$, :'box', :'alice_public', :'bob_secret'), 28 | '22000', 'pgsodium_crypto_box_open: invalid nonce', 'crypto_box_open invalid nonce'); 29 | 30 | SELECT throws_ok(format($$select crypto_box_open(%L, %L, 'bad_key', %L)$$, :'box', :'boxnonce', :'bob_secret'), 31 | '22000', 'pgsodium_crypto_box_open: invalid public key', 'crypto_box_open invalid public key'); 32 | 33 | SELECT throws_ok(format($$select crypto_box_open(%L, %L, %L, 'bad_key')$$, :'box', :'boxnonce', :'alice_public'), 34 | '22000', 'pgsodium_crypto_box_open: invalid secret key', 'crypto_box_open invalid secret key'); 35 | 36 | SELECT throws_ok(format($$select crypto_box_open('foo', %L, %L, %L)$$, :'boxnonce', :'alice_public', :'bob_secret'), 37 | '22000', 'pgsodium_crypto_box_open: invalid message', 'crypto_box_open invalid message'); 38 | 39 | SELECT throws_ok($$select crypto_box_open(NULL, 'bad', 'bad', 'bad')$$, 40 | '22000', 'pgsodium_crypto_box_open: message cannot be NULL', 'crypto_box_open null message'); 41 | 42 | SELECT throws_ok($$select crypto_box_open('bad', NULL, 'bad', 'bad')$$, 43 | '22000', 'pgsodium_crypto_box_open: nonce cannot be NULL', 'crypto_box_open null nonce'); 44 | 45 | SELECT throws_ok($$select crypto_box_open('bad', 'bad', NULL, 'bad')$$, 46 | '22000', 'pgsodium_crypto_box_open: publickey cannot be NULL', 'crypto_box_open null publickey'); 47 | 48 | SELECT throws_ok($$select crypto_box_open('bad', 'bad', 'bad', NULL)$$, 49 | '22000', 'pgsodium_crypto_box_open: secretkey cannot be NULL', 'crypto_box_open null secretkey'); 50 | 51 | SELECT crypto_box_seal('bob is your uncle', :'bob_public') sealed \gset 52 | 53 | SELECT is(crypto_box_seal_open(:'sealed', :'bob_public', :'bob_secret'), 54 | 'bob is your uncle', 'crypto_box_seal/open'); 55 | 56 | SELECT throws_ok(format($$select crypto_box_seal_open(%L, 'bad_key', %L)$$, :'sealed', :'bob_secret'), 57 | '22000', 'pgsodium_crypto_box_seal_open: invalid public key', 'crypto_box_seal_open public key'); 58 | 59 | SELECT throws_ok(format($$select crypto_box_seal_open(%L, %L, 'bad_key')$$, :'sealed', :'bob_public'), 60 | '22000', 'pgsodium_crypto_box_seal_open: invalid secret key', 'crypto_box_seal_open secret key'); 61 | 62 | SELECT throws_ok(format($$select crypto_box_seal_open('foo', %L, %L)$$, :'bob_public', :'bob_secret'), 63 | '22000', 'pgsodium_crypto_box_seal_open: invalid message', 'crypto_box_seal_open invalid message'); 64 | 65 | -------------------------------------------------------------------------------- /test/derive.sql: -------------------------------------------------------------------------------- 1 | \if :serverkeys 2 | 3 | select is(derive_key(1), derive_key(1), 'derived key are equal by id'); 4 | select throws_ok($$select derive_key(NULL)$$, '22000', 'pgsodium_derive: key id cannot be NULL', 'null key id'); 5 | select throws_ok($$select derive_key(1, NULL)$$, '22000', 'pgsodium_derive: key size cannot be NULL', 'null key size'); 6 | select throws_ok($$select derive_key(1, 64, NULL)$$, '22000', 'pgsodium_derive: key context cannot be NULL', 'null key context'); 7 | select isnt(derive_key(1), derive_key(2), 'disequal derived key'); 8 | select is(length(derive_key(2, 64)), 64, 'key len is 64 bytes'); 9 | select isnt(derive_key(2, 32, 'foozball'), derive_key(2, 32), 'disequal context'); 10 | 11 | \endif 12 | -------------------------------------------------------------------------------- /test/hash.sql: -------------------------------------------------------------------------------- 1 | 2 | SELECT crypto_generichash_keygen() generickey \gset 3 | 4 | SELECT is(crypto_generichash('bob is your uncle'), 5 | '\x6c80c5f772572423c3910a9561710313e4b6e74abc0d65f577a8ac1583673657', 6 | 'crypto_generichash'); 7 | 8 | SELECT lives_ok(format($$select crypto_generichash('bob is your uncle', %L::bytea)$$, :'generickey'), 9 | 'crypto_generichash with key'); 10 | 11 | SELECT crypto_shorthash_keygen() shortkey \gset 12 | 13 | SELECT lives_ok(format($$select crypto_shorthash('bob is your uncle', %L::bytea)$$, :'shortkey'), 'crypto_shorthash'); 14 | 15 | SELECT throws_ok($$select crypto_shorthash('bob is your uncle', 's'::bytea)$$, 16 | '22000', 'pgsodium_crypto_shorthash: invalid key', 'crypto_shorthash invalid key'); 17 | 18 | \if :serverkeys 19 | 20 | SELECT lives_ok(format($$select crypto_shorthash('bob is your uncle', 42)$$), 'crypto_shorthash_by_id'); 21 | SELECT lives_ok(format($$select crypto_shorthash('bob is your uncle', 42, '12345678')$$), 'crypto_shorthash by id context'); 22 | 23 | select id as shorthash_key_id from create_key('shorthash') \gset 24 | SELECT lives_ok(format($$select crypto_shorthash('bob is your uncle', %L::uuid)$$, :'shorthash_key_id'), 'crypto_shorthash by uuid'); 25 | 26 | select id as generichash_key_id from create_key('generichash') \gset 27 | SELECT lives_ok(format($$select crypto_generichash('bob is your uncle', %L::uuid)$$, :'generichash_key_id'), 'crypto_generichash by uuid'); 28 | 29 | \endif 30 | -------------------------------------------------------------------------------- /test/helpers.sql: -------------------------------------------------------------------------------- 1 | 2 | select sodium_bin2base64('bob is your uncle') basebob \gset 3 | select throws_ok('select sodium_bin2base64(NULL)', 4 | '22000', 'pgsodium_sodium_bin2base64: bin cannot be NULL', 'sodium_bin2base64 null input'); 5 | 6 | select is(sodium_base642bin(:'basebob'), 'bob is your uncle'::bytea, 'base64'); 7 | 8 | select throws_ok('select sodium_base642bin(NULL)', 9 | '22000', 'pgsodium_sodium_base642bin: base64 cannot be NULL', 'sodium_base642bin null input'); 10 | 11 | -------------------------------------------------------------------------------- /test/hmac.sql: -------------------------------------------------------------------------------- 1 | 2 | select crypto_auth_hmacsha512_keygen() hmac512key \gset 3 | select crypto_auth_hmacsha512('food', :'hmac512key'::bytea) hmac512 \gset 4 | 5 | select throws_ok($$select crypto_auth_hmacsha512(NULL::bytea, 'bad')$$, '22000', 6 | 'pgsodium_crypto_auth_hmacsha512: message cannot be NULL', 'hmac512 null data'); 7 | select throws_ok($$select crypto_auth_hmacsha512('bad', NULL::bytea)$$, '22000', 8 | 'pgsodium_crypto_auth_hmacsha512: key cannot be NULL', 'hmac512 null key'); 9 | 10 | select is(crypto_auth_hmacsha512_verify(:'hmac512', 'food', :'hmac512key'::bytea), true, 'hmac512 verified'); 11 | select is(crypto_auth_hmacsha512_verify(:'hmac512', 'fo0d', :'hmac512key'::bytea), false, 'hmac512 not verified'); 12 | 13 | select throws_ok($$select crypto_auth_hmacsha512_verify(NULL::bytea, 'bad', 'bad')$$, '22000', 14 | 'pgsodium_crypto_auth_hmacsha512_verify: hash cannot be NULL', 'hmac512_verify null hash'); 15 | select throws_ok($$select crypto_auth_hmacsha512_verify('bad', NULL::bytea, 'bad')$$, '22000', 16 | 'pgsodium_crypto_auth_hmacsha512_verify: message cannot be NULL', 'hmac512_verify null message'); 17 | select throws_ok($$select crypto_auth_hmacsha512_verify('bad', 'bad', NULL::bytea)$$, '22000', 18 | 'pgsodium_crypto_auth_hmacsha512_verify: key cannot be NULL', 'hmac512_verify null key'); 19 | 20 | select crypto_auth_hmacsha256_keygen() hmac256key \gset 21 | select crypto_auth_hmacsha256('food', :'hmac256key'::bytea) hmac256 \gset 22 | 23 | select throws_ok($$select crypto_auth_hmacsha256(NULL::bytea, 'bad')$$, '22000', 24 | 'pgsodium_crypto_auth_hmacsha256: message cannot be NULL', 'hmac256 null data'); 25 | select throws_ok($$select crypto_auth_hmacsha256('bad', NULL::bytea)$$, '22000', 26 | 'pgsodium_crypto_auth_hmacsha256: key cannot be NULL', 'hmac256 null key'); 27 | 28 | select is(crypto_auth_hmacsha256_verify(:'hmac256', 'food', :'hmac256key'::bytea), true, 'hmac256 verified'); 29 | select is(crypto_auth_hmacsha256_verify(:'hmac256', 'fo0d', :'hmac256key'::bytea), false, 'hmac256 not verified'); 30 | 31 | select throws_ok($$select crypto_auth_hmacsha256_verify(NULL::bytea, 'bad', 'bad')$$, '22000', 32 | 'pgsodium_crypto_auth_hmacsha256_verify: hash cannot be NULL', 'hmac256_verify null hash'); 33 | select throws_ok($$select crypto_auth_hmacsha256_verify('bad', NULL::bytea, 'bad')$$, '22000', 34 | 'pgsodium_crypto_auth_hmacsha256_verify: message cannot be NULL', 'hmac256_verify null message'); 35 | select throws_ok($$select crypto_auth_hmacsha256_verify('bad', 'bad', NULL::bytea)$$, '22000', 36 | 'pgsodium_crypto_auth_hmacsha256_verify: key cannot be NULL', 'hmac256_verify null key'); 37 | 38 | \if :serverkeys 39 | 40 | select crypto_auth_hmacsha512('food', 42) hmac512 \gset 41 | 42 | select throws_ok($$select crypto_auth_hmacsha512(NULL::bytea, 1)$$, '22000', 43 | 'pgsodium_crypto_auth_hmacsha512_by_id: message cannot be NULL', 'hmac512 null data'); 44 | select throws_ok($$select crypto_auth_hmacsha512('bad', NULL::bigint)$$, '22000', 45 | 'pgsodium_crypto_auth_hmacsha512_by_id: key id cannot be NULL', 'hmac512 null key id'); 46 | select throws_ok($$select crypto_auth_hmacsha512('bad', 1, NULL::bytea)$$, '22000', 47 | 'pgsodium_crypto_auth_hmacsha512_by_id: key context cannot be NULL', 'hmac512 null key context'); 48 | 49 | select is(crypto_auth_hmacsha512_verify(:'hmac512', 'food', 42), true, 'hmac512 verified'); 50 | select is(crypto_auth_hmacsha512_verify(:'hmac512', 'fo0d', 42), false, 'hmac512 not verified'); 51 | 52 | select throws_ok($$select crypto_auth_hmacsha512_verify(NULL::bytea, 'bad', 1)$$, '22000', 53 | 'pgsodium_crypto_auth_hmacsha512_verify_by_id: hash cannot be NULL', 'hmac512_verify null hash'); 54 | select throws_ok($$select crypto_auth_hmacsha512_verify('bad', NULL::bytea, 1)$$, '22000', 55 | 'pgsodium_crypto_auth_hmacsha512_verify_by_id: message cannot be NULL', 'hmac512_verify null message'); 56 | select throws_ok($$select crypto_auth_hmacsha512_verify('bad', 'bad', NULL::bigint)$$, '22000', 57 | 'pgsodium_crypto_auth_hmacsha512_verify_by_id: key id cannot be NULL', 'hmac512_verify null key'); 58 | select throws_ok($$select crypto_auth_hmacsha512_verify('bad', 'bad', 1, NULL::bytea)$$, '22000', 59 | 'pgsodium_crypto_auth_hmacsha512_verify_by_id: key context cannot be NULL', 'hmac512_verify null key'); 60 | 61 | select crypto_auth_hmacsha256('food', 42) hmac256 \gset 62 | 63 | select throws_ok($$select crypto_auth_hmacsha256(NULL::bytea, 1)$$, '22000', 64 | 'pgsodium_crypto_auth_hmacsha256_by_id: message cannot be NULL', 'hmac256 null data'); 65 | select throws_ok($$select crypto_auth_hmacsha256('bad', NULL::bigint)$$, '22000', 66 | 'pgsodium_crypto_auth_hmacsha256_by_id: key id cannot be NULL', 'hmac256 null key id'); 67 | select throws_ok($$select crypto_auth_hmacsha256('bad', 1, NULL::bytea)$$, '22000', 68 | 'pgsodium_crypto_auth_hmacsha256_by_id: key context cannot be NULL', 'hmac256 null key context'); 69 | 70 | select is(crypto_auth_hmacsha256_verify(:'hmac256', 'food', 42), true, 'hmac256 verified'); 71 | select is(crypto_auth_hmacsha256_verify(:'hmac256', 'fo0d', 42), false, 'hmac256 not verified'); 72 | 73 | select throws_ok($$select crypto_auth_hmacsha256_verify(NULL::bytea, 'bad', 1)$$, '22000', 74 | 'pgsodium_crypto_auth_hmacsha256_verify_by_id: hash cannot be NULL', 'hmac256_verify null hash'); 75 | select throws_ok($$select crypto_auth_hmacsha256_verify('bad', NULL::bytea, 1)$$, '22000', 76 | 'pgsodium_crypto_auth_hmacsha256_verify_by_id: message cannot be NULL', 'hmac256_verify null message'); 77 | select throws_ok($$select crypto_auth_hmacsha256_verify('bad', 'bad', NULL::bigint)$$, '22000', 78 | 'pgsodium_crypto_auth_hmacsha256_verify_by_id: key id cannot be NULL', 'hmac256_verify null key'); 79 | select throws_ok($$select crypto_auth_hmacsha256_verify('bad', 'bad', 1, NULL::bytea)$$, '22000', 80 | 'pgsodium_crypto_auth_hmacsha256_verify_by_id: key context cannot be NULL', 'hmac256_verify null key'); 81 | 82 | select crypto_auth_hmacsha256_keygen() extkey256 \gset 83 | select * from pgsodium.create_key('hmacsha256', raw_key:=:'extkey256') \gset extkey256_ 84 | 85 | select crypto_auth_hmacsha512_keygen() extkey512 \gset 86 | select * from pgsodium.create_key('hmacsha512', raw_key:=:'extkey512') \gset extkey512_ 87 | 88 | select crypto_auth_hmacsha512('food', :'extkey512_id'::uuid) hmac512 \gset 89 | 90 | select is(crypto_auth_hmacsha512_verify(:'hmac512', 'food', :'extkey512_id'::uuid), true, 'external hmac512 verified'); 91 | select is(crypto_auth_hmacsha512_verify(:'hmac512', 'fo0d', :'extkey512_id'::uuid), false, 'external hmac512 not verified'); 92 | 93 | select crypto_auth_hmacsha256('food', :'extkey256_id'::uuid) hmac256 \gset 94 | 95 | select is(crypto_auth_hmacsha256_verify(:'hmac256', 'food', :'extkey256_id'::uuid), true, 'external hmac256 verified'); 96 | select is(crypto_auth_hmacsha256_verify(:'hmac256', 'fo0d', :'extkey256_id'::uuid), false, 'external hmac256 not verified'); 97 | 98 | \endif 99 | -------------------------------------------------------------------------------- /test/kdf.sql: -------------------------------------------------------------------------------- 1 | 2 | SELECT crypto_kdf_keygen() kdfkey \gset 3 | SELECT length(crypto_kdf_derive_from_key(64, 1, '__auth__', :'kdfkey'::bytea)) kdfsubkeylen \gset 4 | SELECT is(:kdfsubkeylen, 64, 'kdf byte derived subkey'); 5 | 6 | SELECT length(crypto_kdf_derive_from_key(32, 1, '__auth__', :'kdfkey'::bytea)) kdfsubkeylen \gset 7 | SELECT is(:kdfsubkeylen, 32, 'kdf 32 byte derived subkey'); 8 | 9 | SELECT is(crypto_kdf_derive_from_key(32, 2, '__auth__', :'kdfkey'::bytea), 10 | crypto_kdf_derive_from_key(32, 2, '__auth__', :'kdfkey'::bytea), 'kdf subkeys are deterministic.'); 11 | 12 | SELECT throws_ok($$SELECT crypto_kdf_derive_from_key(NULL, 2, '__aut__', 'bad'::bytea)$$, 13 | '22000', 'pgsodium_crypto_kdf_derive_from_key: subkey size cannot be NULL', 14 | 'kdf null key size.'); 15 | 16 | SELECT throws_ok($$SELECT crypto_kdf_derive_from_key(32, NULL, '__aut__', 'bad'::bytea)$$, 17 | '22000', 'pgsodium_crypto_kdf_derive_from_key: subkey id cannot be NULL', 18 | 'kdf null key size.'); 19 | 20 | SELECT throws_ok($$SELECT crypto_kdf_derive_from_key(32, 1, NULL, 'bad'::bytea)$$, 21 | '22000', 'pgsodium_crypto_kdf_derive_from_key: subkey context cannot be NULL', 22 | 'kdf null key size.'); 23 | 24 | SELECT throws_ok($$SELECT crypto_kdf_derive_from_key(32, 1, '__aut__', NULL::bytea)$$, 25 | '22000', 'pgsodium_crypto_kdf_derive_from_key: primary key cannot be NULL', 26 | 'kdf null key size.'); 27 | 28 | \if :serverkeys 29 | 30 | select id as kdf_key_id from create_key('kdf') \gset 31 | SELECT lives_ok(format($$select crypto_kdf_derive_from_key(32, 42, 'pgsodium', %L::uuid)$$, :'kdf_key_id'), 32 | 'crypto_kdf_derive_from_key by uuid'); 33 | 34 | \endif 35 | -------------------------------------------------------------------------------- /test/keys.sql: -------------------------------------------------------------------------------- 1 | \if :serverkeys 2 | 3 | select * from pgsodium.create_key() \gset anon_det_key_ 4 | 5 | select results_eq( 6 | format($$select 7 | key_id is not null, 8 | key_context is not null, 9 | raw_key is null, 10 | parent_key is null, 11 | raw_key_nonce is null, 12 | name is null, 13 | key_type = 'aead-det' 14 | from pgsodium.key where id = %L$$, :'anon_det_key_id'), 15 | 'values (true, true, true, true, true, true, true)', 16 | 'anon det key asserts'); 17 | 18 | select * from pgsodium.create_key('aead-det', 'foo') \gset foo_det_key_ 19 | 20 | select results_eq( 21 | format($$select 22 | key_id is not null, 23 | key_context is not null, 24 | raw_key is null, 25 | parent_key is null, 26 | raw_key_nonce is null, 27 | key_type = 'aead-det', 28 | name = 'foo' 29 | from pgsodium.key where id = %L$$, :'foo_det_key_id'), 30 | 'values (true, true, true, true, true, true, true)', 31 | 'named det key asserts'); 32 | 33 | 34 | select * from pgsodium.crypto_auth_hmacsha256_keygen() as ext_hmac256_key \gset 35 | select * from pgsodium.create_key('hmacsha256', 'stripe', raw_key:=:'ext_hmac256_key') \gset stripe_hmac256_key_ 36 | 37 | select results_eq( 38 | format($$select 39 | key_id is null, 40 | key_context is null, 41 | raw_key is not null, 42 | parent_key is not null, 43 | raw_key_nonce is not null, 44 | name = 'stripe', 45 | key_type = 'hmacsha256' 46 | from pgsodium.key where id = %L$$,:'stripe_hmac256_key_id'), 47 | 'values (true, true, true, true, true, true, true)', 48 | 'named ext key asserts'); 49 | 50 | select * from pgsodium.create_key( 51 | 'hmacsha256', 'stripe2', parent_key:=:'anon_det_key_id', raw_key:=:'ext_hmac256_key') \gset stripe_hmac256_key_ 52 | 53 | select results_eq( 54 | format($$select 55 | key_id is null, 56 | key_context is null, 57 | raw_key is not null, 58 | parent_key = %L, 59 | raw_key_nonce is not null, 60 | name = 'stripe2', 61 | key_type = 'hmacsha256' 62 | from pgsodium.key where id = %L$$, :'anon_det_key_id', :'stripe_hmac256_key_id'), 63 | 'values (true, true, true, true, true, true, true)', 64 | 'ext key asserts'); 65 | 66 | select results_eq( 67 | format($$select name = 'stripe2' from pgsodium.get_key_by_id(%L)$$, :'stripe_hmac256_key_id'), 68 | 'values (true)', 69 | 'get_key_by_id()'); 70 | 71 | select results_eq($$select name = 'stripe2' from pgsodium.get_key_by_name('stripe2')$$, 72 | 'values (true)', 73 | 'get_key_by_name()'); 74 | 75 | select set_eq($$select name from pgsodium.get_named_keys()$$, 76 | ARRAY['foo', 'OPTIONAL_NAME', 'Optional Name 2', 'stripe', 77 | 'stripe2', 'ANOTHER_NAME', 'Bobo key', 'ietf Test Key', 'det Test Key'], 78 | 'get_named_keys() no filter'); 79 | 80 | select set_eq($$select name from pgsodium.get_named_keys('strip%')$$, 81 | ARRAY['stripe', 'stripe2'], 82 | 'get_named_keys() with filter'); 83 | 84 | -- Test expiring keys 85 | select set_eq($$select id IS NOT NULL from pgsodium.create_key(name => 'notexpired', expires => now() + '1h'::interval)$$, 86 | 'values (true)', 87 | 'creating a key expiring in one hour returns a row'); 88 | 89 | select id as exp_id from pgsodium.key where name = 'notexpired' \gset 90 | 91 | select set_has($$select name from pgsodium.valid_key$$, $$values ('notexpired'::text)$$, 92 | 'view valid_key should list a key expiring in futur'); 93 | 94 | select set_eq(format('select id from pgsodium.get_key_by_id(%L)', :'exp_id' ), 95 | format($$values (%L::uuid)$$, :'exp_id'), 96 | 'pgsodium.get_key_by_id should return a key expiring in futur'); 97 | 98 | select set_eq($$select id from pgsodium.get_key_by_name('notexpired')$$, 99 | format($$values (%L::uuid)$$, :'exp_id'), 100 | 'pgsodium.get_key_by_name should return a key expiring in futur'); 101 | 102 | update pgsodium.key set expires = now() - '1m'::interval, name = 'expired' where name = 'notexpired'; 103 | 104 | select set_hasnt($$select name from pgsodium.valid_key$$, $$values ('expired'::text)$$, 105 | 'view valid_key should not list an expired key'); 106 | 107 | select set_eq(format('select id IS NULL from pgsodium.get_key_by_id(%L)', :'exp_id' ), 108 | 'values (true)', 109 | 'pgsodium.get_key_by_id should not return an expired key'); 110 | 111 | select set_eq($$select id IS NULL from pgsodium.get_key_by_name('expired')$$, 112 | 'values (true)', 113 | 'pgsodium.get_key_by_name should not return an expired key'); 114 | 115 | \endif 116 | -------------------------------------------------------------------------------- /test/kx.sql: -------------------------------------------------------------------------------- 1 | 2 | SELECT public, secret FROM crypto_kx_new_keypair() \gset bob_ 3 | SELECT public, secret FROM crypto_kx_new_keypair() \gset alice_ 4 | 5 | SELECT crypto_kx_new_seed() kxseed \gset 6 | 7 | SELECT public, secret FROM crypto_kx_seed_new_keypair(:'kxseed') \gset seed_bob_ 8 | SELECT public, secret FROM crypto_kx_seed_new_keypair(:'kxseed') \gset seed_alice_ 9 | 10 | select throws_ok($$select crypto_kx_client_session_keys(NULL, 'bad', 'bad')$$, 11 | '22000', 'pgsodium_crypto_kx_client_session_keys: client publickey cannot be NULL', 'kx client null client pk'); 12 | 13 | select throws_ok($$select crypto_kx_client_session_keys('bad', NULL, 'bad')$$, 14 | '22000', 'pgsodium_crypto_kx_client_session_keys: client secretkey cannot be NULL', 'kx client null client sk'); 15 | 16 | select throws_ok($$select crypto_kx_client_session_keys('bad', 'bad', NULL)$$, 17 | '22000', 'pgsodium_crypto_kx_client_session_keys: server publickey cannot be NULL', 'kx client null server pk'); 18 | 19 | select throws_ok($$select crypto_kx_server_session_keys(NULL, 'bad', 'bad')$$, 20 | '22000', 'pgsodium_crypto_kx_server_session_keys: server publickey cannot be NULL', 'kx server null client pk'); 21 | 22 | select throws_ok($$select crypto_kx_server_session_keys('bad', NULL, 'bad')$$, 23 | '22000', 'pgsodium_crypto_kx_server_session_keys: server secretkey cannot be NULL', 'kx server null client sk'); 24 | 25 | select throws_ok($$select crypto_kx_server_session_keys('bad', 'bad', NULL)$$, 26 | '22000', 'pgsodium_crypto_kx_server_session_keys: client publickey cannot be NULL', 'kx server null server pk'); 27 | 28 | SELECT tx, rx FROM crypto_kx_client_session_keys( 29 | :'seed_bob_public', :'seed_bob_secret', 30 | :'seed_alice_public') \gset session_bob_ 31 | 32 | SELECT tx, rx FROM crypto_kx_server_session_keys( 33 | :'seed_alice_public', :'seed_alice_secret', 34 | :'seed_bob_public') \gset session_alice_ 35 | 36 | SELECT crypto_secretbox('hello alice', :'secretboxnonce', :'session_bob_tx'::bytea) bob_to_alice \gset 37 | 38 | SELECT is(crypto_secretbox_open(:'bob_to_alice', :'secretboxnonce', :'session_alice_rx'::bytea), 39 | 'hello alice', 'secretbox_open session key'); 40 | 41 | SELECT crypto_secretbox('hello bob', :'secretboxnonce', :'session_alice_tx'::bytea) alice_to_bob \gset 42 | 43 | SELECT is(crypto_secretbox_open(:'alice_to_bob', :'secretboxnonce', :'session_bob_rx'::bytea), 44 | 'hello bob', 'secretbox_open session key'); 45 | 46 | -------------------------------------------------------------------------------- /test/pwhash.sql: -------------------------------------------------------------------------------- 1 | 2 | SELECT lives_ok($$SELECT crypto_pwhash_saltgen()$$, 'crypto_pwhash_saltgen'); 3 | 4 | SELECT is(crypto_pwhash('Correct Horse Battery Staple', '\xccfe2b51d426f88f6f8f18c24635616b'), 5 | '\x77d029a9b3035c88f186ed0f69f58386ad0bd5252851b4e89f0d7057b5081342', 6 | 'crypto_pwhash'); 7 | 8 | select throws_ok($$select crypto_pwhash(NULL, 'bad')$$, 9 | '22000', 'pgsodium_crypto_pwhash: data cannot be NULL', 'crypto_pwhash NULL password'); 10 | 11 | select throws_ok($$select crypto_pwhash('bad', NULL)$$, 12 | '22000', 'pgsodium_crypto_pwhash: salt cannot be NULL', 'crypto_pwhash NULL salt'); 13 | 14 | SELECT ok(crypto_pwhash_str_verify(crypto_pwhash_str('Correct Horse Battery Staple'), 15 | 'Correct Horse Battery Staple'), 16 | 'crypto_pwhash_str_verify'); 17 | 18 | select throws_ok($$select crypto_pwhash_str(NULL)$$, 19 | '22000', 'pgsodium_crypto_pwhash_str: password cannot be NULL', 'crypto_pwhash_str NULL password'); 20 | 21 | select throws_ok($$select crypto_pwhash_str_verify(NULL, 'bad')$$, 22 | '22000', 'pgsodium_crypto_pwhash_str_verify: hashed password cannot be NULL', 'crypto_pwhash_str_verify NULL hash'); 23 | 24 | select throws_ok($$select crypto_pwhash_str_verify('bad', NULL)$$, 25 | '22000', 'pgsodium_crypto_pwhash_str_verify: password cannot be NULL', 'crypto_pwhash_str_verify NULL password'); 26 | 27 | -------------------------------------------------------------------------------- /test/random.sql: -------------------------------------------------------------------------------- 1 | 2 | SELECT lives_ok($$SELECT randombytes_random()$$, 'randombytes_random'); 3 | SELECT lives_ok($$SELECT randombytes_uniform(10)$$, 'randombytes_uniform'); 4 | SELECT lives_ok($$SELECT randombytes_buf(10)$$, 'randombytes_buf'); 5 | 6 | SELECT throws_ok($$SELECT randombytes_uniform(NULL)$$, 7 | '22000', 'pgsodium_randombytes_uniform: upper bound cannot be NULL', 'randombytes_uniform NULL bound'); 8 | 9 | SELECT throws_ok($$SELECT randombytes_buf(NULL)$$, 10 | '22000', 'pgsodium_randombytes_buf: buffer size cannot be NULL', 'randombytes_buf NULL size'); 11 | 12 | SELECT randombytes_new_seed() bufseed \gset 13 | 14 | SELECT lives_ok(format($$SELECT randombytes_buf_deterministic(10, %L)$$, :'bufseed'), 15 | 'randombytes_buf_deterministic'); 16 | 17 | SELECT throws_ok($$SELECT randombytes_buf_deterministic(NULL, 'bad')$$, 18 | '22000', 'pgsodium_randombytes_buf_deterministic: buffer size cannot be NULL', 'randombytes_buf_deterministic NULL size'); 19 | 20 | SELECT throws_ok($$SELECT randombytes_buf_deterministic(10, NULL)$$, 21 | '22000', 'pgsodium_randombytes_buf_deterministic: seed cannot be NULL', 'randombytes_buf_deterministic NULL seed'); 22 | 23 | -------------------------------------------------------------------------------- /test/secretbox.sql: -------------------------------------------------------------------------------- 1 | SELECT crypto_secretbox_keygen() boxkey \gset 2 | SELECT crypto_secretbox_noncegen() secretboxnonce \gset 3 | 4 | SELECT crypto_secretbox('bob is your uncle', :'secretboxnonce', :'boxkey'::bytea) secretbox \gset 5 | 6 | SELECT throws_ok(format($$select crypto_secretbox(%L, 'bad nonce', %L::bytea)$$, :'secretbox', :'boxkey'), 7 | '22000', 'pgsodium_crypto_secretbox: invalid nonce', 'crypto_secretbox invalid nonce'); 8 | 9 | SELECT throws_ok(format($$select crypto_secretbox(%L, %L, 'bad_key'::bytea)$$, :'secretbox', :'secretboxnonce'), 10 | '22000', 'pgsodium_crypto_secretbox: invalid key', 'crypto_secretbox invalid key'); 11 | 12 | SELECT throws_ok($$select crypto_secretbox(NULL, 'bad', 'bad'::bytea)$$, 13 | '22000', 'pgsodium_crypto_secretbox: message cannot be NULL', 'crypto_secretbox null message'); 14 | 15 | SELECT throws_ok($$select crypto_secretbox('bad', NULL, 'bad'::bytea)$$, 16 | '22000', 'pgsodium_crypto_secretbox: nonce cannot be NULL', 'crypto_secretbox null nonce'); 17 | 18 | SELECT throws_ok($$select crypto_secretbox('bad', 'bad', NULL::bytea)$$, 19 | '22000', 'pgsodium_crypto_secretbox: key cannot be NULL', 'crypto_secretbox null key'); 20 | 21 | SELECT throws_ok($$select crypto_secretbox('bad', 'bad', NULL::bigint)$$, 22 | '22000', 'pgsodium_crypto_secretbox_by_id: key id cannot be NULL', 'crypto_secretbox null key id'); 23 | 24 | SELECT throws_ok($$select crypto_secretbox('bad', 'bad', 1, NULL)$$, 25 | '22000', 'pgsodium_crypto_secretbox_by_id: key context cannot be NULL', 'crypto_secretbox null key context'); 26 | 27 | SELECT is(crypto_secretbox_open(:'secretbox', :'secretboxnonce', :'boxkey'::bytea), 28 | 'bob is your uncle', 'secretbox_open'); 29 | 30 | SELECT throws_ok(format($$select crypto_secretbox_open(%L, 'bad nonce', %L::bytea)$$, :'secretbox', :'boxkey'), 31 | '22000', 'pgsodium_crypto_secretbox_open: invalid nonce', 'crypto_secretbox_open invalid nonce'); 32 | 33 | SELECT throws_ok(format($$select crypto_secretbox_open(%L, %L, 'bad_key'::bytea)$$, :'secretbox', :'secretboxnonce'), 34 | '22000', 'pgsodium_crypto_secretbox_open: invalid key', 'crypto_secretbox_open invalid key'); 35 | 36 | SELECT throws_ok(format($$select crypto_secretbox_open('foo', %L, %L::bytea)$$, :'secretboxnonce', :'boxkey'), 37 | '22000', 'pgsodium_crypto_secretbox_open: invalid message', 'crypto_secretbox_open invalid message'); 38 | 39 | SELECT throws_ok($$select crypto_secretbox_open(NULL, 'bad', 'bad'::bytea)$$, 40 | '22000', 'pgsodium_crypto_secretbox_open: message cannot be NULL', 'crypto_secretbox null message'); 41 | 42 | SELECT throws_ok($$select crypto_secretbox_open('bad', NULL, 'bad'::bytea)$$, 43 | '22000', 'pgsodium_crypto_secretbox_open: nonce cannot be NULL', 'crypto_secretbox null nonce'); 44 | 45 | SELECT throws_ok($$select crypto_secretbox_open('bad', 'bad', NULL::bytea)$$, 46 | '22000', 'pgsodium_crypto_secretbox_open: key cannot be NULL', 'crypto_secretbox null key'); 47 | 48 | SELECT throws_ok($$select crypto_secretbox_open('bad', 'bad', NULL::bigint)$$, 49 | '22000', 'pgsodium_crypto_secretbox_open_by_id: key id cannot be NULL', 'crypto_secretbox null key id'); 50 | 51 | SELECT throws_ok($$select crypto_secretbox_open('bad', 'bad', 1, NULL)$$, 52 | '22000', 'pgsodium_crypto_secretbox_open_by_id: key context cannot be NULL', 'crypto_secretbox null key context'); 53 | 54 | \if :serverkeys 55 | 56 | SET ROLE pgsodium_keyiduser; 57 | 58 | SELECT crypto_secretbox('bob is your uncle', :'secretboxnonce', 1) secretbox \gset 59 | 60 | SELECT is(crypto_secretbox_open(:'secretbox', :'secretboxnonce', 1), 61 | 'bob is your uncle', 'secretbox_open by id'); 62 | 63 | SELECT throws_ok($$select crypto_secretbox('secretbox', 'secretboxnonce', 'whatever'::bytea)$$, 64 | '42501', 'permission denied for function crypto_secretbox', 'crypto_secretbox denied'); 65 | 66 | SELECT throws_ok($$select crypto_secretbox_open('secretbox', 'secretboxnonce', 'whatever'::bytea)$$, 67 | '42501', 'permission denied for function crypto_secretbox_open', 'crypto_secretbox_open denied'); 68 | 69 | SELECT id as secretbox_key_id from create_key('secretbox') \gset 70 | SELECT crypto_secretbox('bob is your uncle', :'secretboxnonce', :'secretbox_key_id'::uuid) secretbox \gset 71 | 72 | SELECT is(crypto_secretbox_open(:'secretbox', :'secretboxnonce', :'secretbox_key_id'::uuid), 73 | 'bob is your uncle', 'secretbox_open by id'); 74 | 75 | RESET ROLE; 76 | \endif 77 | -------------------------------------------------------------------------------- /test/secretstream.sql: -------------------------------------------------------------------------------- 1 | 2 | SELECT crypto_secretstream_keygen() streamkey \gset 3 | -------------------------------------------------------------------------------- /test/sha2.sql: -------------------------------------------------------------------------------- 1 | 2 | select is(crypto_hash_sha256('bob is your uncle'), 3 | '\x5eff82dc2ca0cfbc0d0eaa95b13b7fbec11540e217b0fe2a6f3c7d12f657630d', 'sha256'); 4 | select is(crypto_hash_sha512('bob is your uncle'), 5 | '\xd8adbd01462f1aad1b91a4557d5c865b63dab1b9181cb02f2123f50d210b74a53754a18b09d9f75e38101ce6de04879b35eca91992fade0bb6842f4ea556e952', 6 | 'sha512'); 7 | 8 | select throws_ok($$ select crypto_hash_sha256(NULL)$$, 9 | '22000', 'pgsodium_crypto_hash_sha256: message cannot be NULL', 'sha256 NULL data'); 10 | 11 | select throws_ok($$ select crypto_hash_sha512(NULL)$$, 12 | '22000', 'pgsodium_crypto_hash_sha512: message cannot be NULL', 'sha512 NULL data'); 13 | 14 | -------------------------------------------------------------------------------- /test/sign.sql: -------------------------------------------------------------------------------- 1 | 2 | SELECT lives_ok($$select crypto_sign_seed_new_keypair(crypto_sign_new_seed())$$, 3 | 'crypto_sign_seed_new_keypair'); 4 | 5 | SELECT throws_ok($$select crypto_sign_seed_new_keypair('bogus')$$, '22000', 6 | 'pgsodium_crypto_sign_seed_keypair: invalid seed', 7 | 'crypto_sign_seed_new_keypair invalid seed'); 8 | 9 | SELECT throws_ok($$select crypto_sign_seed_new_keypair(NULL)$$, '22000', 10 | 'pgsodium_crypto_sign_seed_keypair: seed cannot be NULL', 11 | 'crypto_sign_seed_new_keypair NULL seed'); 12 | 13 | SELECT public, secret FROM crypto_sign_new_keypair() \gset sign_ 14 | 15 | SELECT crypto_sign('bob is your uncle', :'sign_secret') signed \gset 16 | 17 | SELECT throws_ok($$select crypto_sign('bob is your uncle', 's')$$, 18 | '22000', 'pgsodium_crypto_sign: invalid secret key', 'crypto_sign invalid key'); 19 | 20 | SELECT throws_ok($$select crypto_sign(NULL, 'bad')$$, 21 | '22000', 'pgsodium_crypto_sign: message cannot be NULL', 'crypto_sign null message'); 22 | 23 | SELECT throws_ok($$select crypto_sign('bad', NULL)$$, 24 | '22000', 'pgsodium_crypto_sign: secretkey cannot be NULL', 'crypto_sign null key'); 25 | 26 | SELECT is(crypto_sign_open(:'signed', :'sign_public'), 27 | 'bob is your uncle', 'crypto_sign_open'); 28 | 29 | SELECT throws_ok(format($$select crypto_sign_open(%L, 'bad_key')$$, :'signed'), 30 | '22000', 'pgsodium_crypto_sign_open: invalid public key', 31 | 'crypto_sign_open invalid public key'); 32 | 33 | SELECT throws_ok(format($$select crypto_sign_open('foo', %L)$$, :'sign_public'), 34 | '22000', 'pgsodium_crypto_sign_open: invalid message', 35 | 'crypto_sign_open invalid message'); 36 | 37 | SELECT throws_ok($$select crypto_sign_open(NULL, 'bad_key')$$, 38 | '22000', 'pgsodium_crypto_sign_open: message cannot be NULL', 39 | 'crypto_sign_open null public key'); 40 | 41 | SELECT throws_ok($$select crypto_sign_open('foo', NULL)$$, 42 | '22000', 'pgsodium_crypto_sign_open: publickey cannot be NULL', 43 | 'crypto_sign_open null message'); 44 | 45 | -- public key signatures 46 | -- We will sign our previously generated sealed box 47 | 48 | SELECT throws_ok($$select crypto_sign_detached('foo', 'bar')$$, 49 | '22000', 'pgsodium_crypto_sign_detached: invalid secret key', 50 | 'crypto_sign_detached invalid secret key'); 51 | 52 | SELECT throws_ok($$select crypto_sign_verify_detached('foo', 'bar', 'bork')$$, 53 | '22000', 'pgsodium_crypto_sign_verify_detached: invalid public key', 54 | 'crypto_sign_verify_detached invalid public key'); 55 | 56 | SELECT crypto_sign_detached('sealed message', :'sign_secret') detached \gset 57 | 58 | SELECT is(crypto_sign_verify_detached(:'detached', 'sealed message', :'sign_public'), 59 | true, 'crypto_sign_detached/verify'); 60 | 61 | SELECT is(crypto_sign_verify_detached(:'detached', 'xyzzy', :'sign_public'), 62 | false, 'crypto_sign_detached/verify (incorrect message)'); 63 | 64 | -- Check Multi-part messages 65 | WITH parts(msg) AS 66 | ( 67 | VALUES ('Hello Alice'), 68 | ('Hello Bob'), 69 | ('Hello Carol') 70 | ), 71 | tampered(msg) AS 72 | ( 73 | VALUES ('Hello Alice'), 74 | ('Hello Bob'), 75 | ('Hello CaRol') 76 | ), 77 | prep1 AS 78 | ( 79 | -- First form of aggregate 80 | SELECT crypto_sign_update_agg(p.msg::bytea) state 81 | FROM parts p 82 | ), 83 | prep2 AS 84 | ( 85 | -- Second form of aggregate 86 | SELECT crypto_sign_update_agg(crypto_sign_init(), p.msg::bytea) state 87 | FROM parts p 88 | ), 89 | prepv AS 90 | ( 91 | -- Second form of aggregate from tampered parts 92 | SELECT crypto_sign_update_agg(crypto_sign_init(), t.msg::bytea) state 93 | FROM tampered t 94 | ), 95 | sig AS 96 | ( 97 | SELECT crypto_sign_final_create(p.state, :'sign_secret') as sig 98 | FROM prep1 p 99 | ), 100 | verify AS 101 | ( 102 | SELECT crypto_sign_final_verify(p.state, s.sig, :'sign_public') as verify 103 | FROM prep1 p 104 | CROSS JOIN sig s 105 | ), 106 | verify2 AS 107 | ( 108 | SELECT crypto_sign_final_verify(p.state, s.sig, :'sign_public') as verify 109 | FROM prep2 p 110 | CROSS JOIN sig s 111 | ), 112 | noverify AS 113 | ( 114 | SELECT crypto_sign_final_verify(p.state, s.sig, :'sign_public') as verify 115 | FROM prepv p 116 | CROSS JOIN sig s 117 | ) 118 | SELECT ok(verify, 'Multi-part signature') 119 | FROM verify 120 | UNION ALL 121 | SELECT ok(verify, 'Multi-part signature(2)') 122 | FROM verify2 123 | -- UNION ALL 124 | -- -- Each time we generate state it will be different, even though sig 125 | -- -- can be verified. 126 | -- SELECT isnt(p1.state, p2.state, 'Multi-part states differ') 127 | -- FROM prep1 p1 CROSS JOIN prep2 p2 128 | UNION ALL 129 | SELECT ok(not verify, 'Multi-part signature detects tampering') 130 | FROM noverify; 131 | 132 | -------------------------------------------------------------------------------- /test/signcrypt.sql: -------------------------------------------------------------------------------- 1 | 2 | SELECT crypto_secretbox_noncegen() secretboxnonce \gset 3 | 4 | SELECT public, secret FROM crypto_signcrypt_new_keypair() \gset bob_ 5 | SELECT public, secret FROM crypto_signcrypt_new_keypair() \gset alice_ 6 | 7 | SELECT state, shared_key FROM crypto_signcrypt_sign_before('bob', 'alice', :'bob_secret', :'alice_public', 'additional data') \gset 8 | 9 | SELECT crypto_secretbox('bob is your uncle', :'secretboxnonce', :'shared_key'::bytea) secretbox \gset 10 | 11 | SELECT crypto_signcrypt_sign_after(:'state', :'bob_secret', :'secretbox') signature \gset 12 | 13 | SELECT state AS vstate, shared_key AS vkey FROM 14 | crypto_signcrypt_verify_before(:'signature', 'bob', 'alice', 'additional data', :'bob_public', :'alice_secret') \gset 15 | 16 | SELECT is(:'vkey'::bytea, :'shared_key'::bytea, 'signcrypt shared keys match'); 17 | 18 | SELECT is(crypto_secretbox_open(:'secretbox', :'secretboxnonce', :'vkey'::bytea), 19 | 'bob is your uncle', 'crypto_aead_ietf_decrypt with signcrypt key'); 20 | 21 | SELECT is(crypto_signcrypt_verify_after(:'vstate', :'signature', :'bob_public', :'secretbox'), 22 | true, 'signcrypt_verify_after'); 23 | 24 | SELECT is(crypto_signcrypt_verify_public(:'signature', 'bob', 'alice', 'additional data', :'bob_public', :'secretbox'), 25 | true, 'signcrypt_verify_public'); 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/stream.sql: -------------------------------------------------------------------------------- 1 | select crypto_stream_xchacha20_noncegen() secretnonce \gset 2 | select crypto_stream_xchacha20_keygen() secretkey \gset 3 | 4 | select crypto_stream_xchacha20_xor('bob is your uncle', :'secretnonce', :'secretkey'::bytea) secret \gset 5 | 6 | SELECT throws_ok(format($$select crypto_stream_xchacha20_xor(%L, 'bad nonce', %L::bytea)$$, :'secret', :'secretkey'), 7 | '22000', 'pgsodium_crypto_stream_xchacha20_xor: invalid nonce', 'crypto_stream invalid nonce'); 8 | 9 | SELECT throws_ok(format($$select crypto_stream_xchacha20_xor(%L, %L, 'bad_key'::bytea)$$, :'secret', :'secretnonce'), 10 | '22000', 'pgsodium_crypto_stream_xchacha20_xor: invalid key', 'crypto_stream invalid key'); 11 | 12 | SELECT is(crypto_stream_xchacha20_xor(:'secret', :'secretnonce', :'secretkey'::bytea), 13 | 'bob is your uncle', 'crypto_stream xor decryption'); 14 | 15 | select crypto_stream_xchacha20_xor_ic('bob is your uncle', :'secretnonce', 42, :'secretkey'::bytea) secret \gset 16 | 17 | SELECT throws_ok(format($$select crypto_stream_xchacha20_xor_ic(%L, 'bad nonce', 42, %L::bytea)$$, :'secret', :'secretkey'), 18 | '22000', 'pgsodium_crypto_stream_xchacha20_xor_ic: invalid nonce', 'crypto_stream invalid nonce'); 19 | 20 | SELECT throws_ok(format($$select crypto_stream_xchacha20_xor_ic(%L, %L, 42, 'bad_key'::bytea)$$, :'secret', :'secretnonce'), 21 | '22000', 'pgsodium_crypto_stream_xchacha20_xor_ic: invalid key', 'crypto_stream invalid key'); 22 | 23 | SELECT is(crypto_stream_xchacha20_xor_ic(:'secret', :'secretnonce', 42, :'secretkey'::bytea), 24 | 'bob is your uncle', 'crypto_stream xor decryption'); 25 | 26 | \if :serverkeys 27 | 28 | select crypto_stream_xchacha20_noncegen() secretnonce \gset 29 | 30 | select crypto_stream_xchacha20_xor('bob is your uncle', :'secretnonce', 42) secret \gset 31 | 32 | SELECT throws_ok(format($$select crypto_stream_xchacha20_xor(%L, 'bad nonce', 42)$$, :'secret'), 33 | '22000', 'pgsodium_crypto_stream_xchacha20_xor_by_id: invalid nonce', 'crypto_stream invalid nonce'); 34 | 35 | SELECT is(crypto_stream_xchacha20_xor(:'secret', :'secretnonce', 42), 36 | 'bob is your uncle', 'crypto_stream xor decryption'); 37 | 38 | select crypto_stream_xchacha20_xor_ic('bob is your uncle', :'secretnonce', 42, 42) secret \gset 39 | 40 | SELECT throws_ok(format($$select crypto_stream_xchacha20_xor_ic(%L, 'bad nonce', 42, 42)$$, :'secret', :'secretkey'), 41 | '22000', 'pgsodium_crypto_stream_xchacha20_xor_ic_by_id: invalid nonce', 'crypto_stream invalid nonce'); 42 | 43 | SELECT is(crypto_stream_xchacha20_xor_ic(:'secret', :'secretnonce', 42, 42), 44 | 'bob is your uncle', 'crypto_stream xor decryption'); 45 | 46 | \endif 47 | -------------------------------------------------------------------------------- /test/tce_rls.sql: -------------------------------------------------------------------------------- 1 | \if :serverkeys 2 | \if :pg15 3 | 4 | CREATE TABLE public.foo( 5 | secret text, 6 | visible bool DEFAULT false 7 | ); 8 | 9 | ALTER TABLE public.foo ENABLE ROW LEVEL SECURITY; 10 | 11 | CREATE POLICY foo_visible ON foo TO pgsodium_keyholder 12 | USING (visible); 13 | 14 | -- Create a key id to use in the tests below 15 | SELECT id AS secret_key_id FROM pgsodium.create_key('aead-det') \gset 16 | 17 | SELECT lives_ok( 18 | format($test$ 19 | SECURITY LABEL FOR pgsodium ON COLUMN public.foo.secret 20 | IS 'ENCRYPT WITH KEY ID %s SECURITY INVOKER' 21 | $test$, :'secret_key_id'), 22 | 'can label column for encryption with security invoker'); 23 | 24 | INSERT INTO public.foo VALUES ('yes', true); 25 | INSERT INTO public.foo VALUES ('no', false); 26 | 27 | CREATE ROLE rls_bobo with login password 'foo'; 28 | GRANT ALL ON public.foo TO rls_bobo; 29 | 30 | SELECT lives_ok( 31 | $test$ 32 | SECURITY LABEL FOR pgsodium ON ROLE rls_bobo is 'ACCESS public.foo' 33 | $test$, 34 | 'can label roles ACCESS for RLS'); 35 | 36 | SELECT pgsodium.update_masks(); -- labeling roles doesn't fire event trigger 37 | 38 | set role rls_bobo; 39 | 40 | SET client_min_messages TO WARNING; 41 | 42 | SELECT results_eq($$SELECT decrypted_secret = 'yes' from public.decrypted_foo$$, 43 | $$VALUES (true)$$, 44 | 'can see updated decrypted view but not excluded row'); 45 | 46 | reset role; 47 | \endif 48 | \endif 49 | -------------------------------------------------------------------------------- /test/test.sql: -------------------------------------------------------------------------------- 1 | \unset ECHO 2 | \set QUIET 1 3 | 4 | \pset format unaligned 5 | \pset tuples_only true 6 | \pset pager off 7 | 8 | \set ON_ERROR_ROLLBACK 1 9 | \set ON_ERROR_STOP on 10 | 11 | SET client_min_messages TO WARNING; 12 | 13 | SELECT EXISTS (SELECT * FROM pg_settings 14 | WHERE name = 'shared_preload_libraries' 15 | AND setting ilike '%pgsodium%') serverkeys \gset 16 | 17 | CREATE EXTENSION IF NOT EXISTS pgtap; 18 | 19 | CREATE EXTENSION IF NOT EXISTS pgsodium; 20 | 21 | BEGIN; 22 | CREATE ROLE bobo with login password 'foo'; 23 | 24 | SELECT * FROM no_plan(); 25 | 26 | \ir pgsodium_schema.sql 27 | 28 | SET search_path = pgsodium, public; 29 | 30 | select (current_setting('server_version_num')::int / 10000) = 15 pg15 \gset 31 | 32 | \ir random.sql 33 | \ir secretbox.sql 34 | \ir secretstream.sql 35 | \ir stream.sql 36 | \ir aead.sql 37 | \ir auth.sql 38 | \ir hash.sql 39 | \ir box.sql 40 | \ir sign.sql 41 | \ir pwhash.sql 42 | \ir kdf.sql 43 | \ir kx.sql 44 | \ir sha2.sql 45 | \ir hmac.sql 46 | \ir derive.sql 47 | \ir signcrypt.sql 48 | \ir helpers.sql 49 | \ir tce.sql 50 | \ir tce_rls.sql 51 | \ir keys.sql 52 | 53 | SELECT * FROM finish(); 54 | 55 | ROLLBACK; 56 | --------------------------------------------------------------------------------