├── test ├── empty.pem ├── bad-pem.pem ├── bad-der.pem ├── dhparam.pem ├── unsupported.pem ├── prime256v1-key.pem ├── secp384r1-key.pem ├── prime256v1-cert.pem ├── secp384r1-cert.pem ├── ec-self-signed.pem ├── dsa-key.pem ├── ca.pem ├── dsa-cert.pem ├── unexpected-eof.pem ├── rsa-cert.pem ├── gnutls-cert.pem ├── gnutls-ca1.pem ├── rsa-key.pem ├── valid-cert.pem ├── encrypted-rsa-key.pem ├── gnutls-ca2.pem ├── dsa-self-signed.pem ├── nested.pem ├── rsa-self-signed.pem ├── expired.pem ├── localhost-new.pem ├── localhost-old.pem ├── text-between.pem ├── new.pem ├── old.pem ├── no-domain.pem ├── gnutls-key.pem └── pkix_test.erl ├── rebar ├── .gitignore ├── CHANGELOG.md ├── tools └── update-ca.sh ├── .github └── workflows │ ├── ci.yml │ └── hexpm-release.yml ├── rebar.config ├── src ├── pkix.app.src ├── pkix_sup.erl ├── pkix_app.erl └── pkix.erl ├── Makefile ├── README.md ├── CODE_OF_CONDUCT.md ├── rebar.config.script ├── CONTRIBUTING.md └── LICENSE /test/empty.pem: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/processone/pkix/HEAD/rebar -------------------------------------------------------------------------------- /test/bad-pem.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | foo bar baz 3 | -----END CERTIFICATE----- 4 | -------------------------------------------------------------------------------- /test/bad-der.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | Zm9vIGJhciBiYXo= 3 | -----END CERTIFICATE----- 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swo 2 | *.swp 3 | .eunit 4 | .rebar 5 | _build 6 | autom4te.cache 7 | c_src/*.d 8 | c_src/*.gcda 9 | c_src/*.gcno 10 | c_src/*.o 11 | config.log 12 | config.status 13 | deps 14 | ebin 15 | priv 16 | rebar.lock 17 | test/*.beam 18 | vars.config 19 | -------------------------------------------------------------------------------- /test/dhparam.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DH PARAMETERS----- 2 | MIGHAoGBALXZ6wi+lHIRcsWsTruCTsvnVPJGlYhY+lrtOR1bK3JwJN/6FKUhp0PC 3 | iivpfjia6UmIJhfPlkf/hXb1ePwbI31BhgItvfrSSjGfQn/9r0vs+XDf8c3hceZh 4 | PXR6k9bU9/6tkkHkjMGZL21syhrCDusV/TNNweClf9Kub57xSymzAgEC 5 | -----END DH PARAMETERS----- 6 | -------------------------------------------------------------------------------- /test/unsupported.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN FOO BAR BAZ----- 2 | MIGHAoGBALXZ6wi+lHIRcsWsTruCTsvnVPJGlYhY+lrtOR1bK3JwJN/6FKUhp0PC 3 | iivpfjia6UmIJhfPlkf/hXb1ePwbI31BhgItvfrSSjGfQn/9r0vs+XDf8c3hceZh 4 | PXR6k9bU9/6tkkHkjMGZL21syhrCDusV/TNNweClf9Kub57xSymzAgEC 5 | -----END FOO BAR BAZ----- 6 | -------------------------------------------------------------------------------- /test/prime256v1-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg8/aBmHNofTvep87k 3 | xPU4tpC6mw7bbV+oYDD2ANQ2of+hRANCAATKVIdqYtoDjpvTMpyXwE3FBoHOV9/F 4 | +5teR8V2OyCHjoqGgNCko+acbKs7nw42fjY1TY+dsgvOOfDJEq2IcJlq 5 | -----END PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /test/secp384r1-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAIT+4322hhOobO6UeV 3 | H0a1CLFCISaVFcDRcOo8WV/f+ZE0sS4J/xqOiuyk3Skb7ouhZANiAAQwtngu/MYf 4 | Z3jjZz3Z2pDCStSC6lZhBGDjrNvPHtQMYShm2KL+RU+dEBGpSfCUx46dIfkNhR7g 5 | fRi8m8jjjU35yuELSEtsWvEXtRbQh7Uo+edSc3grbrs+MtNkx8mseVQ= 6 | -----END PRIVATE KEY----- 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Version 1.0.10 2 | 3 | * Fix issues with building on OTP27 4 | 5 | # Version 1.0.9 6 | 7 | * Generate documentaion for hex.pm packages 8 | * Update CA bundle 9 | 10 | # Version 1.0.8 11 | 12 | * Switch from using Travis to Github Actions as CI 13 | 14 | # Version 1.0.7 15 | 16 | * Update travis config 17 | 18 | # Version 1.0.5 19 | 20 | * Update release year 21 | 22 | # Version 1.0.4 23 | 24 | * Fix unicode formating 25 | * Update CA bundle 26 | * Avoid duplicates in extract\_domain 27 | -------------------------------------------------------------------------------- /test/prime256v1-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBcTCCARigAwIBAgIJANW2AIb52qWQMAoGCCqGSM49BAMCMBQxEjAQBgNVBAMM 3 | CWxvY2FsaG9zdDAeFw0xODA5MjYwNzMyMjJaFw0yODA5MjMwNzMyMjJaMBQxEjAQ 4 | BgNVBAMMCWxvY2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABMpUh2pi 5 | 2gOOm9MynJfATcUGgc5X38X7m15HxXY7IIeOioaA0KSj5pxsqzufDjZ+NjVNj52y 6 | C8458MkSrYhwmWqjUzBRMB0GA1UdDgQWBBTIux2YRYTXGR7fsm+s2vOM4wXRDTAf 7 | BgNVHSMEGDAWgBTIux2YRYTXGR7fsm+s2vOM4wXRDTAPBgNVHRMBAf8EBTADAQH/ 8 | MAoGCCqGSM49BAMCA0cAMEQCIFlbefPdoxDAGpsFMQGUBQkzgWUiYdRknInxCBCm 9 | vPKTAiBfyzgiEA5NGWl5EY5PFoR3kd46zUJ/9HMS74fsAov6dQ== 10 | -----END CERTIFICATE----- 11 | -------------------------------------------------------------------------------- /test/secp384r1-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBrjCCATWgAwIBAgIJAPrgee9NvMsfMAoGCCqGSM49BAMCMBQxEjAQBgNVBAMM 3 | CWxvY2FsaG9zdDAeFw0xODA5MjYwNzMxNTVaFw0xOTA5MjYwNzMxNTVaMBQxEjAQ 4 | BgNVBAMMCWxvY2FsaG9zdDB2MBAGByqGSM49AgEGBSuBBAAiA2IABDC2eC78xh9n 5 | eONnPdnakMJK1ILqVmEEYOOs288e1AxhKGbYov5FT50QEalJ8JTHjp0h+Q2FHuB9 6 | GLybyOONTfnK4QtIS2xa8Re1FtCHtSj551JzeCtuuz4y02THyax5VKNTMFEwHQYD 7 | VR0OBBYEFEbfxJC5h9pKM1ZsvTx9ehkjxCPsMB8GA1UdIwQYMBaAFEbfxJC5h9pK 8 | M1ZsvTx9ehkjxCPsMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDZwAwZAIw 9 | V69in4F5+2O04nSuqTOTHrKjsaOXFhuKJM/wPXIhlIrD7wOK+by+fM1ZwCtjcPE3 10 | AjBVmeyErr5JqovkpeOM+9i/Z5Ko/YYwWLB01pInviwnu+XjLR3fdfl2Q1C5a78A 11 | Dzc= 12 | -----END CERTIFICATE----- 13 | -------------------------------------------------------------------------------- /tools/update-ca.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ################################################################## 3 | # 4 | # Usage: 5 | # - clone the repo: 6 | # $ git clone git@github.com:processone/pkix.git 7 | # - run the script: 8 | # $ pkix/tools/update-ca.sh 9 | # 10 | # NOTES; You absolutely need to run the script from cloned repo 11 | # and have write access to its remote 12 | # 13 | ################################################################## 14 | 15 | SCRIPT_DIR=$(dirname -z $0) 16 | GIT_ROOT=$(git -C $SCRIPT_DIR rev-parse --show-toplevel) 17 | CAFILE=$GIT_ROOT/priv/cacert.pem 18 | 19 | curl --time-cond $CAFILE --output $CAFILE https://curl.haxx.se/ca/cacert.pem 20 | CHANGES=$(git -C $GIT_ROOT diff --name-only $CAFILE) 21 | if [ ! -z "$CHANGES" ]; then 22 | git -C $GIT_ROOT add $CAFILE 23 | git -C $GIT_ROOT commit -m "Update CA bundle" 24 | git -C $GIT_ROOT push 25 | fi 26 | -------------------------------------------------------------------------------- /test/ec-self-signed.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBrjCCATWgAwIBAgIJAPrgee9NvMsfMAoGCCqGSM49BAMCMBQxEjAQBgNVBAMM 3 | CWxvY2FsaG9zdDAeFw0xODA5MjYwNzMxNTVaFw0xOTA5MjYwNzMxNTVaMBQxEjAQ 4 | BgNVBAMMCWxvY2FsaG9zdDB2MBAGByqGSM49AgEGBSuBBAAiA2IABDC2eC78xh9n 5 | eONnPdnakMJK1ILqVmEEYOOs288e1AxhKGbYov5FT50QEalJ8JTHjp0h+Q2FHuB9 6 | GLybyOONTfnK4QtIS2xa8Re1FtCHtSj551JzeCtuuz4y02THyax5VKNTMFEwHQYD 7 | VR0OBBYEFEbfxJC5h9pKM1ZsvTx9ehkjxCPsMB8GA1UdIwQYMBaAFEbfxJC5h9pK 8 | M1ZsvTx9ehkjxCPsMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDZwAwZAIw 9 | V69in4F5+2O04nSuqTOTHrKjsaOXFhuKJM/wPXIhlIrD7wOK+by+fM1ZwCtjcPE3 10 | AjBVmeyErr5JqovkpeOM+9i/Z5Ko/YYwWLB01pInviwnu+XjLR3fdfl2Q1C5a78A 11 | Dzc= 12 | -----END CERTIFICATE----- 13 | -----BEGIN PRIVATE KEY----- 14 | MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAIT+4322hhOobO6UeV 15 | H0a1CLFCISaVFcDRcOo8WV/f+ZE0sS4J/xqOiuyk3Skb7ouhZANiAAQwtngu/MYf 16 | Z3jjZz3Z2pDCStSC6lZhBGDjrNvPHtQMYShm2KL+RU+dEBGpSfCUx46dIfkNhR7g 17 | fRi8m8jjjU35yuELSEtsWvEXtRbQh7Uo+edSc3grbrs+MtNkx8mseVQ= 18 | -----END PRIVATE KEY----- 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | 7 | tests: 8 | name: Tests 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | otp: [20, 21, 25, 26, 27] 13 | runs-on: ubuntu-22.04 14 | container: 15 | image: erlang:${{ matrix.otp }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | - run: make 19 | if: matrix.otp >= 24 20 | - run: REBAR=rebar make 21 | if: matrix.otp < 24 22 | - run: rebar3 compile 23 | - run: rebar3 xref 24 | - run: rebar3 dialyzer 25 | - run: rebar3 eunit -v 26 | - name: Send to Coveralls 27 | if: matrix.otp == 26 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | run: | 31 | COVERALLS=true rebar3 as test coveralls send 32 | curl -v -k https://coveralls.io/webhook \ 33 | --header "Content-Type: application/json" \ 34 | --data '{"repo_name":"$GITHUB_REPOSITORY", 35 | "repo_token":"$GITHUB_TOKEN", 36 | "payload":{"build_num":$GITHUB_RUN_ID, 37 | "status":"done"}}' 38 | -------------------------------------------------------------------------------- /test/dsa-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DSA PRIVATE KEY----- 2 | MIIDVAIBAAKCAQEA1Srx8h3iHzsbpGLMW1pWFo96TLE52KWce89MWqLRUTKBbABw 3 | Z8fX01HOkzZGsdkqxfUc7coZVstIq6fgf/C/4o7S2EK/fSu9uCrMJwbadRL7nNsZ 4 | Wso9HWKnK9nivenIWXxwYlG1aUeLt4PMjQOHSc8IiumSczImXld99WZBnCLflSKD 5 | T3mlqeoy1CPSnmYZWesMOGxWmWNTVvNyaUyLB6+7bOnJ85i/NEoqJC4dpKxoQo1F 6 | M9h4wdDbbh9hiBGsJC3MKqPSL21VAtDILPOspID0IrZdK6pztSmfoXvImQCxj2i0 7 | +wsmBV0vr1A3dIVx4RCZoELAaDU1k/HPFdKthwIhAItIb3nbL0pXRxaShWYPVoZ1 8 | scZ40jKAmb7aVNVOiZYJAoIBAESzmU1C6yxCRjv1AsGACvOgPRPg6PQuD32ankpO 9 | WCW8vudFfeE0GKU5vNOhZoZdA0+h7JuZsThfrFheV0lLlCtGwbQPFG7Uk4Ig59pj 10 | 2zs5tBvApQROMd85DJTXbvuT4fQ5gbBt9tNu+G4+hKUvulbhe+TpOpbl6ZJv/xIv 11 | hhZEK/PYRGaNDfhZTADCZFwwIsjMb5q4EKkED010l4+UN+fd2lshxaJlTvgX7dWB 12 | NErBxyC7P+zQZ6QuLn3S4fyrm8FbHg+NSx0FPb2hcOTwWMHdqUXYvVnFMTWqoqPP 13 | 9IsxI76Wv3S37r8Z6SNkF8uZvWfqTyQsf2CTvMIz3opJgHwCggEAdRfJSU80aCX7 14 | ANL2+o0SeobVBwrlYvwMDAQCMHBrSySquuaXM512NKTqGnN4EUWsWctJUU2owT7M 15 | lWl768ObeLlfK23WYFoKCRChH9Ybj9/og9+sbFRZPf/FoKw1fYkHa4bCNkxO+yXd 16 | 75Ick1pue27+telTNothuVynl1IQKyAdWy1yP82vYYStN4/vIW+H5e0k862kHnnm 17 | OIFA/ext8qMHuyvoE1wLXTIgNx8KesQLJ5T0RMrmVigakSAKpqy/FvnHyEeFBhfG 18 | t9DSgQdM4acWOsXnKa9sVkAZAtGbaUYVStlyhDtkbgiFhlGXWJlNvG4Z/hcjYFbn 19 | HMH+3jR0+QIfOfYob8T6pLrOC117p/ppFkmuPBbDUzNmQQFVlbtUcA== 20 | -----END DSA PRIVATE KEY----- 21 | -------------------------------------------------------------------------------- /test/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDazCCAlOgAwIBAgIUUynLQejEU8NykU/YNfL1dyC7vxcwDQYJKoZIhvcNAQEL 3 | BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM 4 | GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xODA5MjQxMzE4MjRaFw00NjAy 5 | MDkxMzE4MjRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw 6 | HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB 7 | AQUAA4IBDwAwggEKAoIBAQDlbFaRIsreJp2nLa/nsVVzukrfTtpYdCqKuD6rk+t5 8 | EHWcuyuhb5Npd+AHkHX8ZMVK2dJuZzOqmS5bKLiEgH1nMzraRa46lk/HT7DmEfHv 9 | dVbGmytiBoGShCyfwEXzg9+ZEM0bvpM5py6gvN6qc3V15YOA4ZD1rtl6w5Au5kE8 10 | XqjKx8o6kBHTfl9AzpdppHxOLoqjncOQLPDwyLJKsAck6SDGGZfXX6FQyOV1YISJ 11 | GzVUv+CuiVqOp7cH7C7RR2idn9BR5WTgpGcMgAglIrDxeaBwHigjlxSucOc6a/bv 12 | zhGAlmxnG4YXR7mbPm/wGpeoqIXAf8IPWKdSkwDz0wBNAgMBAAGjUzBRMB0GA1Ud 13 | DgQWBBQGU3AZGF8ahVEnpfHB5ETAW5uIBzAfBgNVHSMEGDAWgBQGU3AZGF8ahVEn 14 | pfHB5ETAW5uIBzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAK 15 | jIEjOh7k1xaEMBygQob9XGLmyLgmw1GEvWx7wiDpcdHXuAH9mLC4NPNSjOXPNK2V 16 | u4dh1KHy1z+dHJbt2apXejxtiwlcMWmPDF2EtKjstUN+KXecG7vjReArs71T9ir/ 17 | 7Xfwfg6TKD3H7efYFJaBb7d/lyneNP1Ive/rkRsGqCglkoX4ajcAm7MLkkFD8TCP 18 | NqFc7SdA4OsaeYiUmjnyTUDbKgG0bDAXymhsUzd6Pa9kKQx+dH4GPiCoNoypCXD7 19 | RZSlETNGZ0vdxCjpdvT4eYxSIalG4rAU85turqPF/ovdzUzb72Sta0L5Hrf0rLa/ 20 | um3+Xel8qI+p3kErAG2v 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /.github/workflows/hexpm-release.yml: -------------------------------------------------------------------------------- 1 | name: Hex 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-20.04 11 | 12 | steps: 13 | - name: Check out 14 | uses: actions/checkout@v4 15 | 16 | - name: Setup rebar3 hex 17 | run: | 18 | mkdir -p ~/.config/rebar3/ 19 | echo "{plugins, [rebar3_hex]}." > ~/.config/rebar3/rebar.config 20 | 21 | - run: rebar3 edoc 22 | 23 | - name: Prepare Markdown 24 | run: | 25 | echo "" >>README.md 26 | echo "## EDoc documentation" >>README.md 27 | echo "" >>README.md 28 | echo "You can check this library's " >>README.md 29 | echo "[EDoc documentation](edoc.html), " >>README.md 30 | echo "generated automatically from the source code comments." >>README.md 31 | 32 | - name: Convert Markdown to HTML 33 | uses: natescherer/markdown-to-html-with-github-style-action@v1.1.0 34 | with: 35 | path: README.md 36 | 37 | - run: | 38 | mv doc/index.html doc/edoc.html 39 | mv README.html doc/index.html 40 | 41 | - name: Publish to hex.pm 42 | run: rebar3 hex publish --repo hexpm --yes || head rebar3.crashdump 43 | env: 44 | HEX_API_KEY: ${{ secrets.HEX_API_KEY }} 45 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Evgeny Khramtsov 3 | %%% @copyright (C) 2002-2024 ProcessOne, SARL. All Rights Reserved. 4 | %%% 5 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %%% you may not use this file except in compliance with the License. 7 | %%% You may obtain a copy of the License at 8 | %%% 9 | %%% http://www.apache.org/licenses/LICENSE-2.0 10 | %%% 11 | %%% Unless required by applicable law or agreed to in writing, software 12 | %%% distributed under the License is distributed on an "AS IS" BASIS, 13 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %%% See the License for the specific language governing permissions and 15 | %%% limitations under the License. 16 | %%% 17 | %%%------------------------------------------------------------------- 18 | 19 | {erl_opts, [debug_info]}. 20 | 21 | {cover_enabled, true}. 22 | {cover_export_enabled, true}. 23 | {coveralls_coverdata , "_build/test/cover/eunit.coverdata"}. 24 | {coveralls_service_name , "github"}. 25 | 26 | {xref_checks, [undefined_function_calls, undefined_functions, deprecated_function_calls, deprecated_functions]}. 27 | 28 | 29 | %% Local Variables: 30 | %% mode: erlang 31 | %% End: 32 | %% vim: set filetype=erlang tabstop=8: 33 | -------------------------------------------------------------------------------- /src/pkix.app.src: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Evgeny Khramtsov 3 | %%% @copyright (C) 2002-2024 ProcessOne, SARL. All Rights Reserved. 4 | %%% 5 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %%% you may not use this file except in compliance with the License. 7 | %%% You may obtain a copy of the License at 8 | %%% 9 | %%% http://www.apache.org/licenses/LICENSE-2.0 10 | %%% 11 | %%% Unless required by applicable law or agreed to in writing, software 12 | %%% distributed under the License is distributed on an "AS IS" BASIS, 13 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %%% See the License for the specific language governing permissions and 15 | %%% limitations under the License. 16 | %%% 17 | %%%------------------------------------------------------------------- 18 | {application, pkix, 19 | [{description, "PKIX management"}, 20 | {vsn, "1.0.10"}, 21 | {modules, []}, 22 | {registered, []}, 23 | {applications, [kernel, stdlib, crypto, public_key]}, 24 | {mod, {pkix_app, []}}, 25 | {files, ["src/", "tools/", "priv/", "LICENSE", 26 | "rebar.config", "README.md", "rebar.config.script"]}, 27 | {licenses, ["Apache 2.0"]}, 28 | {links, [{"Github", "https://github.com/processone/pkix"}]}, 29 | {env, []}]}. 30 | 31 | %% Local Variables: 32 | %% mode: erlang 33 | %% End: 34 | %% vim: set filetype=erlang tabstop=8: 35 | -------------------------------------------------------------------------------- /test/dsa-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEYjCCBAigAwIBAgIJALm528GItQgcMAsGCWCGSAFlAwQDAjAUMRIwEAYDVQQD 3 | DAlsb2NhbGhvc3QwHhcNMTgwOTI2MjExMDI1WhcNMjgwOTIzMjExMDI1WjAUMRIw 4 | EAYDVQQDDAlsb2NhbGhvc3QwggNGMIICOQYHKoZIzjgEATCCAiwCggEBANUq8fId 5 | 4h87G6RizFtaVhaPekyxOdilnHvPTFqi0VEygWwAcGfH19NRzpM2RrHZKsX1HO3K 6 | GVbLSKun4H/wv+KO0thCv30rvbgqzCcG2nUS+5zbGVrKPR1ipyvZ4r3pyFl8cGJR 7 | tWlHi7eDzI0Dh0nPCIrpknMyJl5XffVmQZwi35Uig095panqMtQj0p5mGVnrDDhs 8 | VpljU1bzcmlMiwevu2zpyfOYvzRKKiQuHaSsaEKNRTPYeMHQ224fYYgRrCQtzCqj 9 | 0i9tVQLQyCzzrKSA9CK2XSuqc7Upn6F7yJkAsY9otPsLJgVdL69QN3SFceEQmaBC 10 | wGg1NZPxzxXSrYcCIQCLSG952y9KV0cWkoVmD1aGdbHGeNIygJm+2lTVTomWCQKC 11 | AQBEs5lNQussQkY79QLBgArzoD0T4Oj0Lg99mp5KTlglvL7nRX3hNBilObzToWaG 12 | XQNPoeybmbE4X6xYXldJS5QrRsG0DxRu1JOCIOfaY9s7ObQbwKUETjHfOQyU1277 13 | k+H0OYGwbfbTbvhuPoSlL7pW4Xvk6TqW5emSb/8SL4YWRCvz2ERmjQ34WUwAwmRc 14 | MCLIzG+auBCpBA9NdJePlDfn3dpbIcWiZU74F+3VgTRKwccguz/s0GekLi590uH8 15 | q5vBWx4PjUsdBT29oXDk8FjB3alF2L1ZxTE1qqKjz/SLMSO+lr90t+6/GekjZBfL 16 | mb1n6k8kLH9gk7zCM96KSYB8A4IBBQACggEAJUNX5CCVHKQTAKp/cC/N1OoSPqcX 17 | /Wfw0YvnIKsGnBEqFVUU7JZfnJtR9uO69T97ogYML7wfHsA89j8S0t///2yeET9b 18 | pgvm7hAiFDDeN33hpGejn2fYLhAwH+wQGUyEU3YDjQ6iHuEzMZf8BS6G8+6PQbwR 19 | BI3X54Txhi7srnzJ23NvX/USnYeO9MXk4qux2ab6doPxzNruKEbM2KbGr2rct0TJ 20 | 2XMNawYPT06B78kRIKEZcXqNYDh/U+5IPz9Mj/IS8JT93c/Rzm0gcPJCwpYfMGAz 21 | y/h9r7SSidiB9RG7TKELpkPUWZFxSaRJ3Wf9KaB7MhQXLyHSselu2rnehqNTMFEw 22 | HQYDVR0OBBYEFDK0bexGvvOnKJkSiY6R65+BnQ3KMB8GA1UdIwQYMBaAFDK0bexG 23 | vvOnKJkSiY6R65+BnQ3KMA8GA1UdEwEB/wQFMAMBAf8wCwYJYIZIAWUDBAMCA0cA 24 | MEQCIFyCIhX3hKdiHYOg7EyWVLp97zVjIe+B3fauJg+PdW1sAiBjTM7fuxD04Bb2 25 | VpLpq2bLr53hzUVwETGrzKZXWtI09Q== 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /test/unexpected-eof.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE/jCCAuagAwIBAgIJAPQJoxDs27dFMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV 3 | BAMMCWxvY2FsaG9zdDAeFw0xODA5MjYwNzI0MDRaFw0xOTA5MjYwNzI0MDRaMBQx 4 | EjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC 5 | ggIBAN5g4nK81o0N6OlCSz2nHNF+sq/f3SGu4AE8G6KxyeSR6AG5EL53UHhZsFpx 6 | 8dv2sUP8hIpTmymFgvklDnfidaosltrhcijXEP1LD72U4bVYohdf1n1FS24Y+tDO 7 | oKzDXLmwpikXWWEETCAIWlGX3706lYEJExobU7JqjFsNv/eSmfFBeSeMTbSnBr92 8 | gz/uTMiGZ2Xt16ddiCwzA++9ziUUFf/i35jI7wDRS4BrAZ0u1m8dcFxXQLNQuO9m 9 | /mE5m16vnlKy+daguebrBgZzgCb/MnF8c9LexENVI1SjilEdGEEnlTV5CSA70aI9 10 | YmwJ2i1HYRsGl1b50fa2bw60ljYkc/Z9loIELlj+jAdHYEzf+ExmkK/hqsHtwcP6 11 | Pb2E3pxpSX7sV0e4EcaeQ7N32MUrmtrP3NovkUSRjaChMLV9Swb6lKl8jqLfExeN 12 | SGt+EPQJhpo+wRqRxTN5UF1YoMrRQYza5L1GVmB83cgqVIHw1xox5N2Ycx/2zOTD 13 | AeDRqplwL2YociTSZ9cvWMKRJiBTbr7bIaDgTsUjqaAFlzMD00R+5zQeGvZFDWQT 14 | ug94J1dirQv9GsWKs+Erp6jK7HiL11TUvaYB6zNKBoVUN7JFAmxLU9ckPMhdclT9 15 | AGK3XKfln7l9J5turd3crZ6i2F6/3KYA5D51RrPdKjXCBZ0nAgMBAAGjUzBRMB0G 16 | A1UdDgQWBBQ6OUGRx/a+R+14PgZjbGII5el4ZjAfBgNVHSMEGDAWgBQ6OUGRx/a+ 17 | R+14PgZjbGII5el4ZjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IC 18 | AQAN3TbckTbaNDifzQRZck4YRCbgoIadbSofFX+B21IbQVAHH9eqz6eI0Xxgznlf 19 | AeGtdxl/MHhw26rhw/Y9x8SDRx7cVBAarfa9GZT2B6Ot2m1wP8mEJMxlVi3fL1uQ 20 | plPAewIl2Z5vSdmv+LICEFO8TjnzxOUbge/4XRFZm8S1TywmiVhysQ86tHXIJAiF 21 | UZdHLqccR13o9oeZ1dmBlb6y5nD/MicYIgOHuUItD2XeU/YcKfNz5nR31BgPXXuS 22 | ivs3hcARxnfV2F5+5s/y4r9mUGlUc8SBpHN2c9PMXtEqHHgbqvQ2kbKl0QCW3Ggn 23 | dlQ+NT2WMT2fQ2JTUOJuk+Oap72kS1etldqQ3y2VXStfV3yIcfRT4RscK8kL4yv+ 24 | bfttlfzpgru5qfq6npNoV4QYCSwkyEBvJPT/sgOdUMX3Bmq6rEw54kzFHvkGX7W6 25 | 8z6jz8oSPy+kqTYebmBPLNcEWVWGE72TCAotZJ6tBBijHefbUfcwQOGfXqqmqRSM 26 | -------------------------------------------------------------------------------- /test/rsa-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE/jCCAuagAwIBAgIJAPQJoxDs27dFMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV 3 | BAMMCWxvY2FsaG9zdDAeFw0xODA5MjYwNzI0MDRaFw0xOTA5MjYwNzI0MDRaMBQx 4 | EjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC 5 | ggIBAN5g4nK81o0N6OlCSz2nHNF+sq/f3SGu4AE8G6KxyeSR6AG5EL53UHhZsFpx 6 | 8dv2sUP8hIpTmymFgvklDnfidaosltrhcijXEP1LD72U4bVYohdf1n1FS24Y+tDO 7 | oKzDXLmwpikXWWEETCAIWlGX3706lYEJExobU7JqjFsNv/eSmfFBeSeMTbSnBr92 8 | gz/uTMiGZ2Xt16ddiCwzA++9ziUUFf/i35jI7wDRS4BrAZ0u1m8dcFxXQLNQuO9m 9 | /mE5m16vnlKy+daguebrBgZzgCb/MnF8c9LexENVI1SjilEdGEEnlTV5CSA70aI9 10 | YmwJ2i1HYRsGl1b50fa2bw60ljYkc/Z9loIELlj+jAdHYEzf+ExmkK/hqsHtwcP6 11 | Pb2E3pxpSX7sV0e4EcaeQ7N32MUrmtrP3NovkUSRjaChMLV9Swb6lKl8jqLfExeN 12 | SGt+EPQJhpo+wRqRxTN5UF1YoMrRQYza5L1GVmB83cgqVIHw1xox5N2Ycx/2zOTD 13 | AeDRqplwL2YociTSZ9cvWMKRJiBTbr7bIaDgTsUjqaAFlzMD00R+5zQeGvZFDWQT 14 | ug94J1dirQv9GsWKs+Erp6jK7HiL11TUvaYB6zNKBoVUN7JFAmxLU9ckPMhdclT9 15 | AGK3XKfln7l9J5turd3crZ6i2F6/3KYA5D51RrPdKjXCBZ0nAgMBAAGjUzBRMB0G 16 | A1UdDgQWBBQ6OUGRx/a+R+14PgZjbGII5el4ZjAfBgNVHSMEGDAWgBQ6OUGRx/a+ 17 | R+14PgZjbGII5el4ZjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IC 18 | AQAN3TbckTbaNDifzQRZck4YRCbgoIadbSofFX+B21IbQVAHH9eqz6eI0Xxgznlf 19 | AeGtdxl/MHhw26rhw/Y9x8SDRx7cVBAarfa9GZT2B6Ot2m1wP8mEJMxlVi3fL1uQ 20 | plPAewIl2Z5vSdmv+LICEFO8TjnzxOUbge/4XRFZm8S1TywmiVhysQ86tHXIJAiF 21 | UZdHLqccR13o9oeZ1dmBlb6y5nD/MicYIgOHuUItD2XeU/YcKfNz5nR31BgPXXuS 22 | ivs3hcARxnfV2F5+5s/y4r9mUGlUc8SBpHN2c9PMXtEqHHgbqvQ2kbKl0QCW3Ggn 23 | dlQ+NT2WMT2fQ2JTUOJuk+Oap72kS1etldqQ3y2VXStfV3yIcfRT4RscK8kL4yv+ 24 | bfttlfzpgru5qfq6npNoV4QYCSwkyEBvJPT/sgOdUMX3Bmq6rEw54kzFHvkGX7W6 25 | 8z6jz8oSPy+kqTYebmBPLNcEWVWGE72TCAotZJ6tBBijHefbUfcwQOGfXqqmqRSM 26 | 1+rb6fyNBWQPlwB0byspKoa3THZUk2sBZoylf+IlY7sopi4GqZ7rJ8R6NeX7MwCu 27 | 3JcxMhtn/shodKSLWsW7Ua/hEaQ1MFoqQuaS2MhXwfBzjc4LveJVvu9+WI8D9ol9 28 | WWsACCyGYAt7F6/Gv0Z2KbUe6YVoY2fxJinDocmIYw4yMg== 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /src/pkix_sup.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% Created : 22 Sep 2018 by Evgeny Khramtsov 3 | %%% 4 | %%% Copyright (C) 2002-2024 ProcessOne, SARL. All Rights Reserved. 5 | %%% 6 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 7 | %%% you may not use this file except in compliance with the License. 8 | %%% You may obtain a copy of the License at 9 | %%% 10 | %%% http://www.apache.org/licenses/LICENSE-2.0 11 | %%% 12 | %%% Unless required by applicable law or agreed to in writing, software 13 | %%% distributed under the License is distributed on an "AS IS" BASIS, 14 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | %%% See the License for the specific language governing permissions and 16 | %%% limitations under the License. 17 | %%% 18 | %%%------------------------------------------------------------------- 19 | -module(pkix_sup). 20 | -behaviour(supervisor). 21 | 22 | %% API 23 | -export([start_link/0]). 24 | %% Supervisor callbacks 25 | -export([init/1]). 26 | 27 | -define(SHUTDOWN_TIMEOUT, timer:seconds(30)). 28 | 29 | %%%=================================================================== 30 | %%% API functions 31 | %%%=================================================================== 32 | -spec start_link() -> {ok, Pid :: pid()} | 33 | {error, {already_started, Pid :: pid()}} | 34 | {error, {shutdown, term()}} | 35 | {error, term()} | 36 | ignore. 37 | start_link() -> 38 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 39 | 40 | %%%=================================================================== 41 | %%% Supervisor callbacks 42 | %%%=================================================================== 43 | -spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. 44 | init([]) -> 45 | Spec = {pkix, {pkix, start_link, []}, permanent, 46 | ?SHUTDOWN_TIMEOUT, worker, [pkix]}, 47 | {ok, {{one_for_all, 10, 1}, [Spec]}}. 48 | 49 | %%%=================================================================== 50 | %%% Internal functions 51 | %%%=================================================================== 52 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR ?= ./rebar 2 | 3 | IS_REBAR3:=$(shell expr `$(REBAR) --version | awk -F '[ .]' '/rebar / {print $$2}'` '>=' 3) 4 | 5 | all: src 6 | 7 | src: 8 | $(REBAR) get-deps 9 | $(REBAR) compile 10 | 11 | clean: 12 | $(REBAR) clean 13 | 14 | distclean: clean 15 | rm -rf _build 16 | rm -rf deps 17 | rm -rf ebin 18 | rm -rf dialyzer 19 | 20 | test: all 21 | $(REBAR) eunit 22 | 23 | xref: all 24 | $(REBAR) xref 25 | 26 | ifeq "$(IS_REBAR3)" "1" 27 | dialyzer: 28 | $(REBAR) dialyzer 29 | else 30 | deps := $(wildcard deps/*/ebin) 31 | 32 | APPS=kernel stdlib erts public_key crypto asn1 33 | 34 | dialyzer/erlang.plt: 35 | @mkdir -p dialyzer 36 | @dialyzer --build_plt --output_plt dialyzer/erlang.plt \ 37 | -o dialyzer/erlang.log --apps $(APPS); \ 38 | status=$$? ; if [ $$status -ne 2 ]; then exit $$status; else exit 0; fi 39 | 40 | # dialyzer/deps.plt: 41 | # @mkdir -p dialyzer 42 | # @dialyzer --build_plt --output_plt dialyzer/deps.plt \ 43 | # -o dialyzer/deps.log $(deps); \ 44 | # status=$$? ; if [ $$status -ne 2 ]; then exit $$status; else exit 0; fi 45 | 46 | dialyzer/pkix.plt: 47 | @mkdir -p dialyzer 48 | @dialyzer --build_plt --output_plt dialyzer/pkix.plt \ 49 | -o dialyzer/pkix.log ebin; \ 50 | status=$$? ; if [ $$status -ne 2 ]; then exit $$status; else exit 0; fi 51 | 52 | erlang_plt: dialyzer/erlang.plt 53 | @dialyzer --plt dialyzer/erlang.plt --check_plt -o dialyzer/erlang.log; \ 54 | status=$$? ; if [ $$status -ne 2 ]; then exit $$status; else exit 0; fi 55 | 56 | # deps_plt: dialyzer/deps.plt 57 | # @dialyzer --plt dialyzer/deps.plt --check_plt -o dialyzer/deps.log; \ 58 | # status=$$? ; if [ $$status -ne 2 ]; then exit $$status; else exit 0; fi 59 | 60 | pkix_plt: dialyzer/pkix.plt 61 | @dialyzer --plt dialyzer/pkix.plt --check_plt -o dialyzer/pkix.log; \ 62 | status=$$? ; if [ $$status -ne 2 ]; then exit $$status; else exit 0; fi 63 | 64 | dialyzer: erlang_plt pkix_plt #deps_plt pkix_plt 65 | @dialyzer --plts dialyzer/*.plt --no_check_plt \ 66 | --get_warnings -o dialyzer/error.log ebin; \ 67 | status=$$? ; if [ $$status -ne 2 ]; then exit $$status; else exit 0; fi 68 | endif 69 | 70 | check-syntax: 71 | gcc -o nul -S ${CHK_SOURCES} 72 | 73 | .PHONY: clean src test all dialyzer erlang_plt pkix_plt #deps_plt 74 | -------------------------------------------------------------------------------- /test/gnutls-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIGCDCCA/CgAwIBAgIEAkp/4jANBgkqhkiG9w0BAQsFADBHMTMwMQYDVQQDEypQ 3 | cml2YXRlIEludGVybWVkaWF0ZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxEDAOBgNV 4 | BAoTB0V4YW1wbGUwHhcNMTkwNzI0MTY0NTAwWhcNMzgwMTE5MDMxNDA3WjAfMR0w 5 | GwYDVQQDExRlamFiYmVyZC5leGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQAD 6 | ggIPADCCAgoCggIBAKJMHRAX3IRmV8AQvcmkw6wjhRDF2JIlvJIikPgQ8cpPC5+M 7 | Lp2RIHg1UEo3PzEPwNaF1uK6wcQu1kLK6+sQRTjnun2NgkcxsLL9uCDy3kI7c8kv 8 | JgeTil25M7pSnlYeaaIIurlkVN4j0NK1puwNiirMajHTiK70BFeI/avEXyGNa5kl 9 | Q7bvLP/Bx5zUDL5aaO/bkdfiKC1VyoNDj7bxNr/kNQLy/NzU3hKZXDZ0t60tz35e 10 | eP9pmBKfn2PEOJ3ks2vIlnIw1i8Fjz2DtoM2K3WZ/IaA7IjDy7luq8oZJFx6nhyu 11 | C/takL4s9RI3hBEBJGJ1yRE5sKD/dfv82uCOGFaSw0euE29npzYL4e1aQtoNQA6K 12 | sWXb/yfuQmGygKIaCVRK6aBSegM+awI1Se/RixW5EnArvahpNzDe33CpcSbalZ7B 13 | kWiz7qRK7FK+msN7G/68E1hJKwElkEknnTL3IuIUUN9smbE9zkXT2rCZ4aBcfoDu 14 | xtjmVV3VdKebgBcaCMCJx5e9/DW/nVHugtKJ+UjcBKSV7lAt795MUDph11StY0B4 15 | eVQ+vSmRwtxHM59SwO6yG1q0JQsxRMwVbs2McAsoo2hsTXGt7vuv3Cf4I5r2Zxcv 16 | yE5VR3QTN2JF9IZ0ROKUmpEWpRIPPKBDegBK7ik3HUf/+BCg0Ix3sNu2ApWNAgMB 17 | AAGjggEiMIIBHjAMBgNVHRMBAf8EAjAAMCcGA1UdJQQgMB4GCCsGAQUFBwMCBggr 18 | BgEFBQcDAQYIKwYBBQUHAxEwVgYDVR0RBE8wTYIUZWphYmJlcmQuZXhhbXBsZS5j 19 | b22CC2V4YW1wbGUuY29tghJqYWJiZXIuZXhhbXBsZS5jb22CFCouamFiYmVyLmV4 20 | YW1wbGUuY29tMA8GA1UdDwEB/wQFAwMHoAAwHQYDVR0OBBYEFBAJzJv9aqC0s9NK 21 | H9Zb06BeM2+uMB8GA1UdIwQYMBaAFBEmEr/tEMp9+YB/Ciq7oeaoeQMKMDwGA1Ud 22 | HwQ1MDMwMaAvoC2GK2h0dHBzOi8vd3d3LmV4YW1wbGUuY29tL3g1MDkvcmV2b2Nh 23 | dGlvbi5wZW0wDQYJKoZIhvcNAQELBQADggIBAFEHRADXsSZSQMhKODDWmIU4WyDe 24 | WsYopwCc7W7is6EELVTNsecRz9E+jFKaYijTwoh+rieLxzTW0fYl7I6wjVNOY4O8 25 | /RLcXh30Jw9VnHzfYQzONyKXBNbXpqKCts46edJ0QlGXen26ZtOHIGQNl8RMmE44 26 | k6OlhHRMu0LBnz6GQJT6TLmMNnvHcBRSqqsfHN9X4F/Lvhu7Vuu3JTaesQz9tQyj 27 | mV+VXDg/ua8hBZgvDz3GupUfi6tZ7BGTnBe41aVlwcDBLgOkTU+3f7QXFO4yz/Ou 28 | WGlJucUjusDLfgIIvRsN89b1faW56XZNiE3FkpJ6HuuNZp4H1LyqKtMpGftSbCJT 29 | 4duYVBX5K8GRC3A+r2Obf29aMXFQV6MUISq9v3s8wLybiVEAInDXO42gyVdSJq+N 30 | PLT8+yQpGr2ZurNGHUcLh+wM8l+H/xLKdFpGBC3PD5SlqQyb3Bpw5dkrimD2b5Wx 31 | rlZ9suK1iurF/YQ4r/sZdnrKniLo6gTQ9/ylAxjrI0GMyuIRg3kLjQMM1knca84j 32 | bHMiHRp68eeevfwQRTCNkhpH6E93MXy+pndfWjaA/7hTYSKCJhytgrrOfLdI+C7I 33 | bH+2M0c6asOezz7jzU6/of/axi3J9DJ1yUCp7kxTkgUvi/ZsQ3bC1iKcawVueho3 34 | x3PTdwG7E/Yt3NuV 35 | -----END CERTIFICATE----- 36 | -------------------------------------------------------------------------------- /src/pkix_app.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% Created : 22 Sep 2018 by Evgeny Khramtsov 3 | %%% 4 | %%% Copyright (C) 2002-2024 ProcessOne, SARL. All Rights Reserved. 5 | %%% 6 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 7 | %%% you may not use this file except in compliance with the License. 8 | %%% You may obtain a copy of the License at 9 | %%% 10 | %%% http://www.apache.org/licenses/LICENSE-2.0 11 | %%% 12 | %%% Unless required by applicable law or agreed to in writing, software 13 | %%% distributed under the License is distributed on an "AS IS" BASIS, 14 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | %%% See the License for the specific language governing permissions and 16 | %%% limitations under the License. 17 | %%% 18 | %%%------------------------------------------------------------------- 19 | -module(pkix_app). 20 | -behaviour(application). 21 | 22 | %% Application callbacks 23 | -export([start/2, start_phase/3, stop/1, prep_stop/1, 24 | config_change/3]). 25 | 26 | %%%=================================================================== 27 | %%% Application callbacks 28 | %%%=================================================================== 29 | -spec start(StartType :: normal | 30 | {takeover, Node :: node()} | 31 | {failover, Node :: node()}, 32 | StartArgs :: term()) -> 33 | {ok, Pid :: pid()} | 34 | {ok, Pid :: pid(), State :: term()} | 35 | {error, Reason :: term()}. 36 | start(_StartType, _StartArgs) -> 37 | pkix_sup:start_link(). 38 | 39 | -spec start_phase(Phase :: atom(), 40 | StartType :: normal | 41 | {takeover, Node :: node()} | 42 | {failover, Node :: node()}, 43 | PhaseArgs :: term()) -> ok | {error, Reason :: term()}. 44 | start_phase(_Phase, _StartType, _PhaseArgs) -> 45 | ok. 46 | 47 | -spec stop(State :: term()) -> any(). 48 | stop(_State) -> 49 | ok. 50 | 51 | -spec prep_stop(State :: term()) -> NewState :: term(). 52 | prep_stop(State) -> 53 | State. 54 | 55 | -spec config_change(Changed :: [{Par :: atom(), Val :: term()}], 56 | New :: [{Par :: atom(), Val :: term()}], 57 | Removed :: [Par :: atom()]) -> ok. 58 | config_change(_Changed, _New, _Removed) -> 59 | ok. 60 | 61 | %%%=================================================================== 62 | %%% Internal functions 63 | %%%=================================================================== 64 | -------------------------------------------------------------------------------- /test/gnutls-ca1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 43 | -----END CERTIFICATE----- 44 | -------------------------------------------------------------------------------- /test/rsa-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQDeYOJyvNaNDejp 3 | Qks9pxzRfrKv390hruABPBuiscnkkegBuRC+d1B4WbBacfHb9rFD/ISKU5sphYL5 4 | JQ534nWqLJba4XIo1xD9Sw+9lOG1WKIXX9Z9RUtuGPrQzqCsw1y5sKYpF1lhBEwg 5 | CFpRl9+9OpWBCRMaG1OyaoxbDb/3kpnxQXknjE20pwa/doM/7kzIhmdl7denXYgs 6 | MwPvvc4lFBX/4t+YyO8A0UuAawGdLtZvHXBcV0CzULjvZv5hOZter55SsvnWoLnm 7 | 6wYGc4Am/zJxfHPS3sRDVSNUo4pRHRhBJ5U1eQkgO9GiPWJsCdotR2EbBpdW+dH2 8 | tm8OtJY2JHP2fZaCBC5Y/owHR2BM3/hMZpCv4arB7cHD+j29hN6caUl+7FdHuBHG 9 | nkOzd9jFK5raz9zaL5FEkY2goTC1fUsG+pSpfI6i3xMXjUhrfhD0CYaaPsEakcUz 10 | eVBdWKDK0UGM2uS9RlZgfN3IKlSB8NcaMeTdmHMf9szkwwHg0aqZcC9mKHIk0mfX 11 | L1jCkSYgU26+2yGg4E7FI6mgBZczA9NEfuc0Hhr2RQ1kE7oPeCdXYq0L/RrFirPh 12 | K6eoyux4i9dU1L2mAeszSgaFVDeyRQJsS1PXJDzIXXJU/QBit1yn5Z+5fSebbq3d 13 | 3K2eothev9ymAOQ+dUaz3So1wgWdJwIDAQABAoICADnK5A79lKTD4Kv+Vp+HNq4b 14 | R0T94PJICF9Jx8TYf6evY6RO/FMDnx4n8PUQd9K6ogcRMUDhyYlY8VjekIwHhpzb 15 | SblIrep/OiMZxtV2Q9JlXnrEpXGY+Kl8RHLPwGIlw4tepVA4iTn1a/NHOHHMDpGX 16 | EBOg0B4QzAeqOR/QyvsEVo2kfmAQzoEMh2xq2GFdBQd4JpcO4OTWIfWarIM5yMFK 17 | Hw2JYKLMVZDY085kAN9gtMnb1L+qzV3MtMNC+Qk7d218JqFHcjadPrMMkVxL2BpK 18 | aEmmFlPPISJ+ldgBSIkcDpmgN97VvdFqumh3m4SIavMpgcsMQ3iPJEoBz4r6065y 19 | apw0W3J/h5nyI62dpVu3jhIUBVeJvDkV0zRZrWLHqLRnlQ1sSC/3cYxcB1fZ+CYL 20 | kBTDmcpUsUipC48YslhtiTzgo6rDOXj2Sqpt/nLtfNiRuxOiT2NbuEcQiYziHvrJ 21 | UE+n4lCDaT+UA3eRxqKvtJZlFXJHn7OtJ7si6rJA9WfTsjeX6isiOxWUHQxFTxgS 22 | jmUUQ1zNa2rivHZwgqmoXTh6qCn0moqPaKIvl3I+aS+AjnkJSnlWmL6RXBGZWl+A 23 | ixrn5kGCnlj9/tKnWAE4WJ9C9B/0VdwPlszf1ot2WUCtAt1yzwfAB44lK3xSnY2G 24 | ab17OFfAYH665oCNobSBAoIBAQD1MKz+beAJnwy+353gymqwqxjCYBs8dQshAiXY 25 | N5AtZxDlXNtlEK0HrZ8seZzSMGI9swzxOVmYq4XRkgo4nC+R4nV48CL+MclVEdUn 26 | ma9hFxm/sKiblp2paWbwajFwFgYzVjnxNjZ5I8a5AQd+pEsnuzeIdiSeixZlKY9f 27 | TsyiFosb48YVD7TmMAFkN/5yYZ1PtzizHsrzqRGvnkIE1/NQOnqdi6G+ap1T3Hmt 28 | QTc+RTS3ATnql7oH6ouaz7uJzhQjD3n/ISIZB596uZt12QjFEORhvcSPGRFhw9LZ 29 | Fyh/a1hxkKfPdGR6B+43GFel+i594fa836jtPkL8YEm12hvfAoIBAQDoLr79qTDn 30 | h5R9RjUx37FpeRD6xjsyj35+pUBjpsGlsY/W/cIrr4N8yJ8A8dS7yF8GlW6Utc9+ 31 | EqLReZnzrlvV+t6OBQfF2ChNd7BpKAwi/dmWnfMenIO3APkDTGr7Pfp7XNHjR3il 32 | nF7i9bWmTehKgK26sKA/+qApdI3dTI2T3f6Pi4u47zKIM6Ejd9Ew/oJEq/xvyk4J 33 | YeDu8Z4EstTCSJKwpiyTU4gMcEKn+wqkAelJs9pfalr56s4E1uucFXs2PSlPCmW3 34 | HCh8kz8mPGJPVk0t9Ezk5PD6rT0j+tqNATKAJnrOlGTIaKOU2DsctFdl0rR7XOtB 35 | NJWAZPw356e5AoIBABjdRM3QaqXPIUXB+4quPD+KOkqL4HczD1vbkebpS4+vIgGA 36 | dyc6l7Fto/SoqISQL4Y+QBO+Ux2uVzW3b53qCNDsfCk1gPbyKY6c7lbDuQtJgmz0 37 | B0Uv8vEZJT1AJ59MPHi8R8f0TXXfcOmV9yKampx+2dTW1kPVqwG7QzTREuNlEdpU 38 | MOQ14YxuVdXJ7929lGxfEjrn5oDrJkX+8Ib5N01tgL21SUO8IBQ/CX/OW0HPVtcw 39 | IqVtmYnv+RRL+KKy6Uvc5+w9ee273ipd5CwBRGhnnCFlUyeHzUFy0FD/jjXNtvxd 40 | RQc+sGnHoBqqN0k3o7tUTOc+by0P4U8inJb6dVUCggEAHgkeD/Z5Kxpw7RvN+du0 41 | Oa69sZM/STUi6gM6pymFV4f20ZsWKUyVN+lEGH0wRfKPyGxAV+CFLQwAIBUZA1sE 42 | 6lN/wuOHs+JYpFzdZ10U5Nnt8fwQ3V7l8yCfFfwmwsWrx5WkWUB/rPzjkXyzuQXP 43 | DQREdSgwMtabLYG0cHJcxkoriipFMvFOmiwDpnDzkOD7vSJ6j4OeQLx2urJq/LSd 44 | rVxyDYQRtCVULje/h7eEEt9kbHJlx34cssPbTuj2pcRpogSbeWwg6GUuH590xd99 45 | 4EGLzmwSHnI3clZC2Iq1BxSmkclojZzIxNw0fSbTzszNmZB+ZI8Kp+7DgE6QCjNf 46 | kQKCAQBPzPty1WCmtCOoB0ovbeqQkw3IGr+dtiKd/YL5hBT//U/a9so2MPV4mD30 47 | EeyqpS2hgEs/49uhHN+gzgM+rYwlIzOEkzyST5sClRcCtEzyPxEE398cDyQaIl+E 48 | ofjVBLG6LbTZLCQ0aSYVfY+KksS20gI1dnHhK/RGMm7tZEuA68m/PHSNzLp/Ca9m 49 | WQimGkqX1+xTUu+jaFeZ+CrP8YshFJQxi22iXqHwaQCB901jNBDHeBCUYhQAsc9k 50 | 8ECq/0PHjpEO27QsMPE7v/tJbAo79yok3rm9SZ5wzs+yzbf8Sd92hCQU/Ml9bJfY 51 | IOzzRLCPWuLjE1QSqY8Ahqz1498n 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /test/valid-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEjTCCA3WgAwIBAgIBATANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJBVTET 3 | MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ 4 | dHkgTHRkMB4XDTE4MDkyNDEzMTgyNFoXDTQ2MDIwOTEzMTgyNFowWTELMAkGA1UE 5 | BhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdp 6 | ZGdpdHMgUHR5IEx0ZDESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B 7 | AQEFAAOCAQ8AMIIBCgKCAQEA1oQMN4MZ/wEf4SM7chwHZ+ymQ5Knt45VZ0jmgpnK 8 | Fx0p+eJoNegvvwY/80NWTmcgbGnqruJiOh5AEUNDtCD5G/70oz2WHgZBZkuLsopE 9 | a/2sDmwxvUbv1f/mD8iHcDaWUvKAy4TUHFeHDQL28HJom9E7bgYadeuhebwZcsbu 10 | lPFePw+fWM7jLWxkMYClfsdzsBrgerbZVPnAuj77cGXZSQ6p96jOPiJ/mjOVCwWJ 11 | tdlqwme2AC4AwKYdWzc3Ysw8lES/ubMa+lP1Eh9aI8edpHIlC5nYNLVTWa4Xw6Ct 12 | AvqzKtNNJzwypbR3fcDXaWvvO3GY3wOHVC/wyCsL8SXc7QIDAQABo4IBcjCCAW4w 13 | CQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2Vy 14 | dGlmaWNhdGUwHQYDVR0OBBYEFFvDi47v5xJKOsgQo8MP4JzY6cC/MB8GA1UdIwQY 15 | MBaAFAZTcBkYXxqFUSel8cHkRMBbm4gHMDMGA1UdHwQsMCowKKAmoCSGImh0dHA6 16 | Ly9sb2NhbGhvc3Q6NTI4MC9kYXRhL2NybC5kZXIwNgYIKwYBBQUHAQEEKjAoMCYG 17 | CCsGAQUFBzABhhpodHRwOi8vbG9jYWxob3N0OjUyODAvb2NzcDALBgNVHQ8EBAMC 18 | BeAwJwYDVR0lBCAwHgYIKwYBBQUHAwkGCCsGAQUFBwMBBggrBgEFBQcDAjBQBgNV 19 | HREESTBHggsqLmxvY2FsaG9zdKA4BggrBgEFBQcIBaAsDCp0ZXN0X3NpbmdsZSEj 20 | JCVeKigpYH4rLTtfPVtde318XEBsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEB 21 | AEW8qvdyBMOSjCwJ1G178xsxf8Adw/9QN2ftBGKCo1C3YtmP5CvipChq5FTrOvRz 22 | XjoQxbKhlqEumkZQkfmLiM/DLbkFeNqGWpuy14lkyIPUknaLKNCJX++pXsJrPLGR 23 | btWnlB0cb+pLIB/UkG8OIpW07pNOZxHdHoHInRMMs89kgsmhIpn5OamzPWK/bqTB 24 | YjAPIdmdkYk9oxWfgjpJ4BG2PbGS6CnjA29j7vebuQ4ebVpFBMI9w77PY3NcuMK7 25 | ML6MV6ez/+nPpz+E4zRxsVxmVAbSaiFDW3G3efAybDeT5QW1x/oJm2SpsJNIGHcp 26 | RecYNo9esOTG+Bg6wypg4WA= 27 | -----END CERTIFICATE----- 28 | -----BEGIN RSA PRIVATE KEY----- 29 | MIIEpgIBAAKCAQEA1oQMN4MZ/wEf4SM7chwHZ+ymQ5Knt45VZ0jmgpnKFx0p+eJo 30 | NegvvwY/80NWTmcgbGnqruJiOh5AEUNDtCD5G/70oz2WHgZBZkuLsopEa/2sDmwx 31 | vUbv1f/mD8iHcDaWUvKAy4TUHFeHDQL28HJom9E7bgYadeuhebwZcsbulPFePw+f 32 | WM7jLWxkMYClfsdzsBrgerbZVPnAuj77cGXZSQ6p96jOPiJ/mjOVCwWJtdlqwme2 33 | AC4AwKYdWzc3Ysw8lES/ubMa+lP1Eh9aI8edpHIlC5nYNLVTWa4Xw6CtAvqzKtNN 34 | JzwypbR3fcDXaWvvO3GY3wOHVC/wyCsL8SXc7QIDAQABAoIBAQDUwGX1cHsJ5C2f 35 | 9ndwtsfJlHVZs0vPysR9CVpE0Q4TWoNVJ+0++abRB/vI4lHotHL90xZEmJXfGj1k 36 | YZf2QHWQBI7Qj7Yg1Qdr0yUbz/IIQLCyJTA3jvEzBvc/VByveBQi9Aw0zOopqc1x 37 | ZC1RT8bcMumEN11q8mVV/O4oXZAl+mQIbRRt6JIsRtoW8hpB1e2ipHItDMNpSnzA 38 | 6PqcddDyDDePgi5lMOaeV9un60A6pI/+uvmw16R1Io+DyYRnxds3HJ/ccI0Co1P1 39 | khA75QLdnoniYO+oQrq/wGvm+Uq1seh6iuj+SOWvCdB03vPmGYxPKMSW9AtX8xbJ 40 | J9lboi3pAoGBAPBaiUYn9F+Zt9oJTHhAimZgs1ub5xVEFwVhYJtFBT3E1rQWRKuf 41 | kiU1JRq7TB3MGaC4zGi2ql12KV3AqFhwLKG6sKtlo/IJhJfe3DgWmBVYBBifkgYs 42 | mxmA6opgyjbjDEMn6RA+Jov5H267AsnaB4cCB1Jjra6GIdIoMvPghHZXAoGBAOR6 43 | 7VC6E+YX5VJPCZiN0h0aBT+Hl4drYQKvZHp5N8RIBkvmcQHEJgsrUKdirFZEXW6y 44 | WvepwI4C/Xl61y64/DZ7rum/gpAEPdzSkefKysHAiqkMRcIpjiRxTPJ547ZJycjP 45 | E+jzcYfLwQvCW9ZiYl+KdYRbpqBFQC8aWqixFxRbAoGBAJQTsy79vpiHY7V4tRwA 46 | 50NboCR4UE3RvT0bWSFPzILZmk0oyvXRQYCa1Vk6uxJAhCl4sLZyk1MxURrpbs3N 47 | jjG1itKNtAuRwZavPo1vnhLIPv3MkXIsWQHFYroOF4bpKszU8cmIAMeLm8nkfTtO 48 | kASlQ02HC6HSEVQgYAPP9svRAoGBANiOnwKl7Bhpy8TQ/zJmMaG9uP23IeuL3l4y 49 | KdVfsXjMH5OvLqtS5BAwFPkiMGBv2fMC/+/AKK8xrFiJEw3I7d0iK+6Hw1OHga8c 50 | soh1kOpF+ecyp6fZxU1LSniFCU0M8UHw7Fke7RueBzKDHJK9m6oczTgPuoYsPSKo 51 | IwfDGjIDAoGBAMJVkInntV8oDPT1WYpOAZ3Z0myCDZVBbjxx8kE4RSJIsFeNSiTO 52 | nhLWCqoG11PVTUzhpYItCjp4At/dG8OQY7WWm0DJJQB38fEqA6JKWpgeWwUdkk8j 53 | anCrNUBEuzt3UPSZ17DGCw2+J+mwsg1nevaFIXy0gN2zPtTBWtacznPL 54 | -----END RSA PRIVATE KEY----- 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PKIX certificates management library for Erlang 2 | =============================================== 3 | 4 | [![CI](https://github.com/processone/pkix/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/processone/pkix/actions/workflows/ci.yml) 5 | [![Coverage Status](https://coveralls.io/repos/processone/pkix/badge.svg?branch=master&service=github)](https://coveralls.io/github/processone/pkix?branch=master) 6 | [![Hex version](https://img.shields.io/hexpm/v/pkix.svg "Hex version")](https://hex.pm/packages/pkix) 7 | 8 | The idea of the library is to simplify certificates configuration in Erlang programs. 9 | Typically an Erlang program which needs certificates (for HTTPS/MQTT/XMPP/etc) 10 | provides a bunch of options such as `certfile`, `chainfile`, `privkey`, etc. 11 | The situation becomes even more complicated when a server supports so called `virtual domains` 12 | because a program is typically required to match a virtual domain with its certificate. 13 | If a user has plenty of virtual domains (stored somewhere in `/etc/letsencrypt/live/*/*.pem`) 14 | it's quickly becoming a nightmare for them to configure all this. The complexity also leads to 15 | errors: a single configuration mistake and a program generates obscure log messages, 16 | unreadable Erlang tracebacks or, even worse, just silently ignores the errors. Fortunately, 17 | the large part of certificates configuration can be automated, reducing a user configuration 18 | to something as simple as: 19 | ```yaml 20 | certfiles: 21 | - /etc/letsencrypt/live/*/*.pem 22 | ``` 23 | The purpose of the library is to do this dirty job under the hood. 24 | 25 | # System requirements 26 | 27 | To compile the library you need: 28 | 29 | - Erlang/OTP ≥ 19.0 30 | - GNU Make. Optional: for running tests or standalone compilation. 31 | 32 | # Compiling 33 | 34 | Since this is an embedded library, you need to add https://github.com/processone/pkix.git 35 | repo to your rebar configuration or what have you. 36 | 37 | # Usage 38 | 39 | Start the library as a regular Erlang application: 40 | ```erl 41 | > application:ensure_all_started(pkix). 42 | ``` 43 | or use `pkix:start()` which does the same. 44 | 45 | Let's say you have two certificates: `cert1.pem` for `domain1` and `cert2.pem` 46 | for `domain2` with their private keys `key1.pem` and `key2.pem` and 47 | an intermediate CA certificate `ca-intermediate.pem`. Then the flow is the following: 48 | - Add all your PEM files to the "staged" area (the order doesn't matter): 49 | ```erl 50 | > pkix:add_file("cert1.pem"). 51 | > pkix:add_file("cert2.pem"). 52 | > pkix:add_file("key1.pem"). 53 | > pkix:add_file("key2.pem"). 54 | > pkix:add_file("ca-intermediate.pem"). 55 | ``` 56 | - Commit the changes to some directory, let's say, `"/tmp/certs"`: 57 | ```erl 58 | > pkix:commit("/tmp/certs"). 59 | ``` 60 | Now you're able to fetch a certificate file containing full chain and the 61 | private key for domain `domain1` or `domain2`: 62 | ```erl 63 | > pkix:get_certfile(<<"domain1">>). 64 | {<<"/tmp/certs/7f9faada4a006091531cd37dafb70ca009630ac3">>,undefined,undefined} 65 | > pkix:get_certfile(<<"domain2">>). 66 | {undefined,<<"/tmp/certs/018e601430ed447e4bb767b2d610c6258c7a4e43">>,undefined} 67 | ``` 68 | The first element of the tuple is an EC certificate (presented in `cert1.pem`), 69 | the second element is an RSA certificate (presented in `cert2.pem`) and the third element 70 | is a DSA certificate (missing in our example). 71 | 72 | # API 73 | TODO. Sorry, read the [source](https://github.com/processone/pkix/blob/master/src/pkix.erl) so far. 74 | -------------------------------------------------------------------------------- /test/encrypted-rsa-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | 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 53 | DZsCzwpriekjstKOniFb/g== 54 | -----END ENCRYPTED PRIVATE KEY----- 55 | -------------------------------------------------------------------------------- /test/gnutls-ca2.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIJxDCCBaygAwIBAgIBADANBgkqhkiG9w0BAQsFADA6MSYwJAYDVQQDEx1Qcml2 3 | YXRlIENlcnRpZmljYXRlIEF1dGhvcml0eTEQMA4GA1UEChMHRXhhbXBsZTAeFw0x 4 | NDA0MDcxNzI3MDBaFw0zODAxMTkwMzE0MDdaMDoxJjAkBgNVBAMTHVByaXZhdGUg 5 | Q2VydGlmaWNhdGUgQXV0aG9yaXR5MRAwDgYDVQQKEwdFeGFtcGxlMIIEIjANBgkq 6 | hkiG9w0BAQEFAAOCBA8AMIIECgKCBAEA4lsl67c6lIsHKJ+KK+w5FgmGy1Hf5VVp 7 | Yx/RWfJPz8pCzdEiiDKB/KWqbQcwHrcSlzhEMQEDcC9fJDwnvWEtiQejg+qq8qIh 8 | /XWLNP95Jm9tqudgPphGI0nHwbAokk6famVDLJtntAvFfhBAjgXICjExhPSSwhSS 9 | LjLIw5DCl0sm/l6hpn4eB6SUMOZDsRcrOmTWqjjVpMbVGdc1EqudQx/rd4NPmorE 10 | a4qW71LEHRwwoKv1mpWd7l4ZThl6plg3QSS+CfwtdHfiJ2fnhQo10m7WH0Ju9QKr 11 | wmJtbeBGcoXMK0Fzo8jfcLRpvg6zhu6vh5Y2gi9MtEzHNxxPGddPnWEm4ggE0rWD 12 | 6JX2P9b6X3ephb9rAiMOSEyR6jQhIbNVLQojh2EYHVkZM/fI0noU2NtO2MaH2ggB 13 | 15wCzn2DBaA2xy7M0phvF5wWiOHyiBnIsA2PFMDD+U73nU7oARqRD1AYMqrWH3cG 14 | LGck1RP9I2DUJgToJBzSz7ovHhj11TRPe2bayC6H3wkuEl39Klx3hI81dQdmKBZn 15 | a/IoDFR8+VuuKajurVgEZ0wYqcytgY8OYW2qRo5UrDoE8vx/xEEvvxBwXafit6DF 16 | iuMsUIA1RPbFbrfpxP4T3/a63wB45dIXho0ErMhgUJTxWqTbc1tWn/FgBuMPPbDN 17 | ew0pKv2dlf9oy1jfv/JoA8bPczZWXxk1un2MX1BpIjIN2lNO2Qgzh2i9B4pOR6t7 18 | B58DQG74xCD28JuhMMtW1jlM1B/HgAFFfxVxyCSJSEaicXsJVORRW/xahqxFErj6 19 | fJIDGHKGi7MImf/30xdKVxx5WDbV8/9AJYmBVIYKqkes5Jt8wl7knenVyfxe0RO2 20 | EuQMMVE4eY72kjwOsJ/7vBgYvbXVrFBhnHvx+EVKr7YlXffSfnL02WOtyO9FsnQ/ 21 | Fo/CIIVOLr0v2okGM5A0GQ+9xeVC+7gtsZK22mYHDwvakCIfRPut8qvkGl9+Mprn 22 | LZexsamVSWCSfpYqCzcmB/B2RQZU5OREl5inUNbJAg5harfNV5sqXaqfN/qOTAfO 23 | 0aycudSOaQgsV4wh65JB+y8qQxwu3r4EpqrekqCq89vKGkrCJ9mCWw3B086E9MjD 24 | /G0TcT+QnFP/cRKNK95H+kni2pztsiFe10kj2wCCI1WWEXFvITe0mIxvR0QKWL3o 25 | CbLYaUTXvfjygw0TN5s2EJw62XP0hiOYMKqwrCibJ0Zv5tDyC8Wb8ges3ZLRPbQk 26 | ev+7B7ZpWUgfu0QeJvgveKV7flYxOZiDrdzAwF2dIbFBpMEVEz2eI3AyYZnIfXRI 27 | rC4rz/ktJGqfaf0/0ETCBpbk5fVGKGHc7O6OrY5J5UU90NmyhbPn0wIDAQABo4HU 28 | MIHRMA8GA1UdEwEB/wQFMAMBAf8wUAYDVR0gBEkwRzBFBgwrBgEEAYLVEJlQIQAw 29 | NTAzBggrBgEFBQcCARYnaHR0cHM6Ly93d3cuZXhhbXBsZS5jb20veDUwOS9wb2xp 30 | Y3kudHh0MA8GA1UdDwEB/wQFAwMHBgAwHQYDVR0OBBYEFLHIkSOAQfS8IYDfbYlW 31 | IYyvsHdvMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHBzOi8vd3d3LmV4YW1wbGUuY29t 32 | L3g1MDkvcmV2b2NhdGlvbi5wZW0wDQYJKoZIhvcNAQELBQADggQBAJdlk2zGW/sc 33 | EvNGKFz3FBcJNUSJ85TUo3KX4UJ1v9kt+ZRHnnSAR0wXUJ2pfZJVwGBpTbtuDOQP 34 | 1khs5jPTcL9NLfxaJBAKLWPwo4p3aNvzbwHJY1l2dbKXoHeWm7bPJsIzjIDLgapF 35 | 2i/qt1SEnEhuewxj4u8q28IsxGBt4gfybW14nYCYwDSbfDAIuFnx7mqAPvkx12Db 36 | B57res48STrTqoJ9rcKBFPPq2cryFCTxJ2OEqfYf+mZa5C6P980JNJ2BjUjAE7r+ 37 | c+K+ZjC+LgPdSUI4jaiTr+cfb7O0ZYQvswywfDjuhCU44dKUxAc/MA+ncGOB9euF 38 | aHGtkCj9vlvajXUqBOx8id3RDT9kasN09UEQrlSaXl9p7KKQyLzkoFD+HebOVte0 39 | v8FS9By8nEqRH/dQFjz2jXp5CJubET7VsHnnmPN9qVTDBOv6lZBkFP+j3r0eWqSM 40 | hH6kIuHl4R7ctWM9mpjxZvEoM/aZgtol8+kDm0yMMZ2E+q2toAJOcnxHkAxb+mba 41 | jpX9YlgMJpcDhM/DrgPFyKf4CE+3AF6SMiLkKMuG9mCv3fKqSBfxANmAl8ewKeAC 42 | Ph6hsIMpFDGNA5M9cGDKW267xAyYGwgyYLyglvnMvs7fQuEGf4KWof+TH60cfsZG 43 | CAZj4xcaNpqatY57p3r6p4wMYgnltn6Y5DDip0E+IIZNlfNQlyFZ+tt4ThXJp4Wd 44 | U4mbQpWWPA82PtrmGvmC1Dv00/8fbRVNk3t5VjhRkmORwrU4ZcgIZgWo2g1GErXA 45 | QussB3EnLV0+mrya3QdfovgoVj6ipZ4BggJ1mWZwIwe+XRYbgXQ/3dPCpbpjjNXP 46 | U7tLWO007XoOZ/lfPqIte+68LEET576nOy1qqTUrEtdi7x0e24EL1M928xeKl6B4 47 | ng7Oa1ZtO48eBcvnP7J292+fhNStnV6fRS1Urq/kuh7oR7+LVqs9bs7Sanl1W21X 48 | epSNVtbxRYdww7h7/tD6WxC0HF4uS+x2RnJbMOIZEyjkqVpToizozdxGJzSlWusy 49 | cjaIrRQfImoLf/CS5VQMlN0J8zNnBxzR5j/l95PTZvixtqxtf63AUUIGa5VBC1sj 50 | c3x8ih1v3CtmNihXqM7aoA8CD+wV2Q8GFAY1yDB1H7GbbuFO7Rr/ZA4fbAJLCVuj 51 | 0P24Wvx49sKtUCIh8ewO3T/HvEC/cGmf5dwEOB4iHepo79ic+tnr1sfVGhzSlOne 52 | MaOn8hFSQncaozucDNpSrAdy48A5xzfI3e7gnUjsMiPaRyshPKBaGLz08+ktjeM1 53 | pBEbNzWahF5dugTvwq94gXMfXwfw1HaCww7PwSmd/cM9UVYsbBugKKNIU6GEjK9o 54 | buTOx0yhiRQ= 55 | -----END CERTIFICATE----- 56 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at contact@process-one.net. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /test/dsa-self-signed.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEYjCCBAigAwIBAgIJALm528GItQgcMAsGCWCGSAFlAwQDAjAUMRIwEAYDVQQD 3 | DAlsb2NhbGhvc3QwHhcNMTgwOTI2MjExMDI1WhcNMjgwOTIzMjExMDI1WjAUMRIw 4 | EAYDVQQDDAlsb2NhbGhvc3QwggNGMIICOQYHKoZIzjgEATCCAiwCggEBANUq8fId 5 | 4h87G6RizFtaVhaPekyxOdilnHvPTFqi0VEygWwAcGfH19NRzpM2RrHZKsX1HO3K 6 | GVbLSKun4H/wv+KO0thCv30rvbgqzCcG2nUS+5zbGVrKPR1ipyvZ4r3pyFl8cGJR 7 | tWlHi7eDzI0Dh0nPCIrpknMyJl5XffVmQZwi35Uig095panqMtQj0p5mGVnrDDhs 8 | VpljU1bzcmlMiwevu2zpyfOYvzRKKiQuHaSsaEKNRTPYeMHQ224fYYgRrCQtzCqj 9 | 0i9tVQLQyCzzrKSA9CK2XSuqc7Upn6F7yJkAsY9otPsLJgVdL69QN3SFceEQmaBC 10 | wGg1NZPxzxXSrYcCIQCLSG952y9KV0cWkoVmD1aGdbHGeNIygJm+2lTVTomWCQKC 11 | AQBEs5lNQussQkY79QLBgArzoD0T4Oj0Lg99mp5KTlglvL7nRX3hNBilObzToWaG 12 | XQNPoeybmbE4X6xYXldJS5QrRsG0DxRu1JOCIOfaY9s7ObQbwKUETjHfOQyU1277 13 | k+H0OYGwbfbTbvhuPoSlL7pW4Xvk6TqW5emSb/8SL4YWRCvz2ERmjQ34WUwAwmRc 14 | MCLIzG+auBCpBA9NdJePlDfn3dpbIcWiZU74F+3VgTRKwccguz/s0GekLi590uH8 15 | q5vBWx4PjUsdBT29oXDk8FjB3alF2L1ZxTE1qqKjz/SLMSO+lr90t+6/GekjZBfL 16 | mb1n6k8kLH9gk7zCM96KSYB8A4IBBQACggEAJUNX5CCVHKQTAKp/cC/N1OoSPqcX 17 | /Wfw0YvnIKsGnBEqFVUU7JZfnJtR9uO69T97ogYML7wfHsA89j8S0t///2yeET9b 18 | pgvm7hAiFDDeN33hpGejn2fYLhAwH+wQGUyEU3YDjQ6iHuEzMZf8BS6G8+6PQbwR 19 | BI3X54Txhi7srnzJ23NvX/USnYeO9MXk4qux2ab6doPxzNruKEbM2KbGr2rct0TJ 20 | 2XMNawYPT06B78kRIKEZcXqNYDh/U+5IPz9Mj/IS8JT93c/Rzm0gcPJCwpYfMGAz 21 | y/h9r7SSidiB9RG7TKELpkPUWZFxSaRJ3Wf9KaB7MhQXLyHSselu2rnehqNTMFEw 22 | HQYDVR0OBBYEFDK0bexGvvOnKJkSiY6R65+BnQ3KMB8GA1UdIwQYMBaAFDK0bexG 23 | vvOnKJkSiY6R65+BnQ3KMA8GA1UdEwEB/wQFMAMBAf8wCwYJYIZIAWUDBAMCA0cA 24 | MEQCIFyCIhX3hKdiHYOg7EyWVLp97zVjIe+B3fauJg+PdW1sAiBjTM7fuxD04Bb2 25 | VpLpq2bLr53hzUVwETGrzKZXWtI09Q== 26 | -----END CERTIFICATE----- 27 | -----BEGIN DSA PRIVATE KEY----- 28 | MIIDVgIBAAKCAQEA1Srx8h3iHzsbpGLMW1pWFo96TLE52KWce89MWqLRUTKBbABw 29 | Z8fX01HOkzZGsdkqxfUc7coZVstIq6fgf/C/4o7S2EK/fSu9uCrMJwbadRL7nNsZ 30 | Wso9HWKnK9nivenIWXxwYlG1aUeLt4PMjQOHSc8IiumSczImXld99WZBnCLflSKD 31 | T3mlqeoy1CPSnmYZWesMOGxWmWNTVvNyaUyLB6+7bOnJ85i/NEoqJC4dpKxoQo1F 32 | M9h4wdDbbh9hiBGsJC3MKqPSL21VAtDILPOspID0IrZdK6pztSmfoXvImQCxj2i0 33 | +wsmBV0vr1A3dIVx4RCZoELAaDU1k/HPFdKthwIhAItIb3nbL0pXRxaShWYPVoZ1 34 | scZ40jKAmb7aVNVOiZYJAoIBAESzmU1C6yxCRjv1AsGACvOgPRPg6PQuD32ankpO 35 | WCW8vudFfeE0GKU5vNOhZoZdA0+h7JuZsThfrFheV0lLlCtGwbQPFG7Uk4Ig59pj 36 | 2zs5tBvApQROMd85DJTXbvuT4fQ5gbBt9tNu+G4+hKUvulbhe+TpOpbl6ZJv/xIv 37 | hhZEK/PYRGaNDfhZTADCZFwwIsjMb5q4EKkED010l4+UN+fd2lshxaJlTvgX7dWB 38 | NErBxyC7P+zQZ6QuLn3S4fyrm8FbHg+NSx0FPb2hcOTwWMHdqUXYvVnFMTWqoqPP 39 | 9IsxI76Wv3S37r8Z6SNkF8uZvWfqTyQsf2CTvMIz3opJgHwCggEAJUNX5CCVHKQT 40 | AKp/cC/N1OoSPqcX/Wfw0YvnIKsGnBEqFVUU7JZfnJtR9uO69T97ogYML7wfHsA8 41 | 9j8S0t///2yeET9bpgvm7hAiFDDeN33hpGejn2fYLhAwH+wQGUyEU3YDjQ6iHuEz 42 | MZf8BS6G8+6PQbwRBI3X54Txhi7srnzJ23NvX/USnYeO9MXk4qux2ab6doPxzNru 43 | KEbM2KbGr2rct0TJ2XMNawYPT06B78kRIKEZcXqNYDh/U+5IPz9Mj/IS8JT93c/R 44 | zm0gcPJCwpYfMGAzy/h9r7SSidiB9RG7TKELpkPUWZFxSaRJ3Wf9KaB7MhQXLyHS 45 | selu2rnehgIhAIZq6uyx6khGW7+jxM+Q5cRRBFFHE1gXpKZC9GVkETiZ 46 | -----END DSA PRIVATE KEY----- 47 | -----BEGIN DSA PRIVATE KEY----- 48 | MIIDVgIBAAKCAQEA1Srx8h3iHzsbpGLMW1pWFo96TLE52KWce89MWqLRUTKBbABw 49 | Z8fX01HOkzZGsdkqxfUc7coZVstIq6fgf/C/4o7S2EK/fSu9uCrMJwbadRL7nNsZ 50 | Wso9HWKnK9nivenIWXxwYlG1aUeLt4PMjQOHSc8IiumSczImXld99WZBnCLflSKD 51 | T3mlqeoy1CPSnmYZWesMOGxWmWNTVvNyaUyLB6+7bOnJ85i/NEoqJC4dpKxoQo1F 52 | M9h4wdDbbh9hiBGsJC3MKqPSL21VAtDILPOspID0IrZdK6pztSmfoXvImQCxj2i0 53 | +wsmBV0vr1A3dIVx4RCZoELAaDU1k/HPFdKthwIhAItIb3nbL0pXRxaShWYPVoZ1 54 | scZ40jKAmb7aVNVOiZYJAoIBAESzmU1C6yxCRjv1AsGACvOgPRPg6PQuD32ankpO 55 | WCW8vudFfeE0GKU5vNOhZoZdA0+h7JuZsThfrFheV0lLlCtGwbQPFG7Uk4Ig59pj 56 | 2zs5tBvApQROMd85DJTXbvuT4fQ5gbBt9tNu+G4+hKUvulbhe+TpOpbl6ZJv/xIv 57 | hhZEK/PYRGaNDfhZTADCZFwwIsjMb5q4EKkED010l4+UN+fd2lshxaJlTvgX7dWB 58 | NErBxyC7P+zQZ6QuLn3S4fyrm8FbHg+NSx0FPb2hcOTwWMHdqUXYvVnFMTWqoqPP 59 | 9IsxI76Wv3S37r8Z6SNkF8uZvWfqTyQsf2CTvMIz3opJgHwCggEAJUNX5CCVHKQT 60 | AKp/cC/N1OoSPqcX/Wfw0YvnIKsGnBEqFVUU7JZfnJtR9uO69T97ogYML7wfHsA8 61 | 9j8S0t///2yeET9bpgvm7hAiFDDeN33hpGejn2fYLhAwH+wQGUyEU3YDjQ6iHuEz 62 | MZf8BS6G8+6PQbwRBI3X54Txhi7srnzJ23NvX/USnYeO9MXk4qux2ab6doPxzNru 63 | KEbM2KbGr2rct0TJ2XMNawYPT06B78kRIKEZcXqNYDh/U+5IPz9Mj/IS8JT93c/R 64 | zm0gcPJCwpYfMGAzy/h9r7SSidiB9RG7TKELpkPUWZFxSaRJ3Wf9KaB7MhQXLyHS 65 | selu2rnehgIhAIZq6uyx6khGW7+jxM+Q5cRRBFFHE1gXpKZC9GVkETiZ 66 | -----END DSA PRIVATE KEY----- 67 | -------------------------------------------------------------------------------- /rebar.config.script: -------------------------------------------------------------------------------- 1 | %%%---------------------------------------------------------------------- 2 | %%% File : rebar.config.script 3 | %%% Author : Mickael Remond 4 | %%% Purpose : Rebar build script. Compliant with rebar and rebar3. 5 | %%% Created : 24 Nov 2015 by Mickael Remond 6 | %%% 7 | %%% Copyright (C) 2002-2024 ProcessOne, SARL. All Rights Reserved. 8 | %%% 9 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 10 | %%% you may not use this file except in compliance with the License. 11 | %%% You may obtain a copy of the License at 12 | %%% 13 | %%% http://www.apache.org/licenses/LICENSE-2.0 14 | %%% 15 | %%% Unless required by applicable law or agreed to in writing, software 16 | %%% distributed under the License is distributed on an "AS IS" BASIS, 17 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | %%% See the License for the specific language governing permissions and 19 | %%% limitations under the License. 20 | %%% 21 | %%%---------------------------------------------------------------------- 22 | 23 | IsRebar3 = case application:get_key(rebar, vsn) of 24 | {ok, VSN} -> 25 | [VSN1 | _] = string:tokens(VSN, "-"), 26 | [Maj|_] = string:tokens(VSN1, "."), 27 | (list_to_integer(Maj) >= 3); 28 | undefined -> 29 | lists:keymember(mix, 1, application:loaded_applications()) 30 | end, 31 | 32 | ModCfg0 = fun(F, Cfg, [Key|Tail], Op, Default) -> 33 | {OldVal,PartCfg} = case lists:keytake(Key, 1, Cfg) of 34 | {value, {_, V1}, V2} -> {V1, V2}; 35 | false -> {if Tail == [] -> Default; true -> [] end, Cfg} 36 | end, 37 | case Tail of 38 | [] -> 39 | [{Key, Op(OldVal)} | PartCfg]; 40 | _ -> 41 | [{Key, F(F, OldVal, Tail, Op, Default)} | PartCfg] 42 | end 43 | end, 44 | ModCfg = fun(Cfg, Keys, Op, Default) -> ModCfg0(ModCfg0, Cfg, Keys, Op, 45 | Default) end, 46 | 47 | ModCfgS = fun(Cfg, Keys, Val) -> ModCfg0(ModCfg0, Cfg, Keys, fun(_V) -> 48 | Val end, "") end, 49 | 50 | 51 | FilterConfig = fun(F, Cfg, [{Path, true, ModFun, Default} | Tail]) -> 52 | F(F, ModCfg0(ModCfg0, Cfg, Path, ModFun, Default), Tail); 53 | (F, Cfg, [_ | Tail]) -> 54 | F(F, Cfg, Tail); 55 | (F, Cfg, []) -> 56 | Cfg 57 | end, 58 | 59 | AppendStr = fun(Append) -> 60 | fun("") -> 61 | Append; 62 | (Val) -> 63 | Val ++ " " ++ Append 64 | end 65 | end, 66 | AppendList = fun(Append) -> 67 | fun(Val) -> 68 | Val ++ Append 69 | end 70 | end, 71 | 72 | Rebar3DepsFilter = fun(DepsList) -> 73 | lists:map(fun({DepName,_, {git,_, {tag,Version}}}) -> 74 | {DepName, Version}; 75 | (Dep) -> 76 | Dep 77 | end, DepsList) 78 | end, 79 | 80 | GlobalDepsFilter = fun(Deps) -> 81 | DepNames = lists:map(fun({DepName, _, _}) -> DepName; 82 | ({DepName, _}) -> DepName 83 | end, Deps), 84 | lists:filtermap(fun(Dep) -> 85 | case code:lib_dir(Dep) of 86 | {error, _} -> 87 | {true,"Unable to locate dep '"++atom_to_list(Dep)++"' in system deps."}; 88 | _ -> 89 | false 90 | end 91 | end, DepNames) 92 | end, 93 | 94 | GithubConfig = case {os:getenv("GITHUB_ACTIONS"), os:getenv("GITHUB_TOKEN")} of 95 | {"true", Token} when is_list(Token) -> 96 | CONFIG1 = [{coveralls_repo_token, Token}, 97 | {coveralls_service_job_id, os:getenv("GITHUB_RUN_ID")}, 98 | {coveralls_commit_sha, os:getenv("GITHUB_SHA")}, 99 | {coveralls_service_number, os:getenv("GITHUB_RUN_NUMBER")}], 100 | case os:getenv("GITHUB_EVENT_NAME") =:= "pull_request" 101 | andalso string:tokens(os:getenv("GITHUB_REF"), "/") of 102 | [_, "pull", PRNO, _] -> 103 | [{coveralls_service_pull_request, PRNO} | CONFIG1]; 104 | _ -> 105 | CONFIG1 106 | end; 107 | _ -> 108 | [] 109 | end, 110 | 111 | Rules = [ 112 | {[deps], IsRebar3, 113 | Rebar3DepsFilter, []}, 114 | {[plugins], IsRebar3, 115 | AppendList([pc]), []}, 116 | {[plugins], os:getenv("COVERALLS") == "true", 117 | AppendList([{coveralls, {git, 118 | "https://github.com/processone/coveralls-erl.git", 119 | {branch, "addjsonfile"}}} ]), []}, 120 | {[deps], os:getenv("USE_GLOBAL_DEPS") /= false, 121 | GlobalDepsFilter, []} 122 | ], 123 | 124 | 125 | Config = FilterConfig(FilterConfig, CONFIG, Rules) ++ GithubConfig, 126 | 127 | %io:format("Rules:~n~p~n~nCONFIG:~n~p~n~nConfig:~n~p~n", [Rules, CONFIG, Config]), 128 | 129 | Config. 130 | 131 | %% Local Variables: 132 | %% mode: erlang 133 | %% End: 134 | %% vim: set filetype=erlang tabstop=8: 135 | -------------------------------------------------------------------------------- /test/nested.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE/jCCAuagAwIBAgIJAPQJoxDs27dFMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV 3 | BAMMCWxvY2FsaG9zdDAeFw0xODA5MjYwNzI0MDRaFw0xOTA5MjYwNzI0MDRaMBQx 4 | EjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC 5 | ggIBAN5g4nK81o0N6OlCSz2nHNF+sq/f3SGu4AE8G6KxyeSR6AG5EL53UHhZsFpx 6 | 8dv2sUP8hIpTmymFgvklDnfidaosltrhcijXEP1LD72U4bVYohdf1n1FS24Y+tDO 7 | oKzDXLmwpikXWWEETCAIWlGX3706lYEJExobU7JqjFsNv/eSmfFBeSeMTbSnBr92 8 | gz/uTMiGZ2Xt16ddiCwzA++9ziUUFf/i35jI7wDRS4BrAZ0u1m8dcFxXQLNQuO9m 9 | /mE5m16vnlKy+daguebrBgZzgCb/MnF8c9LexENVI1SjilEdGEEnlTV5CSA70aI9 10 | YmwJ2i1HYRsGl1b50fa2bw60ljYkc/Z9loIELlj+jAdHYEzf+ExmkK/hqsHtwcP6 11 | Pb2E3pxpSX7sV0e4EcaeQ7N32MUrmtrP3NovkUSRjaChMLV9Swb6lKl8jqLfExeN 12 | SGt+EPQJhpo+wRqRxTN5UF1YoMrRQYza5L1GVmB83cgqVIHw1xox5N2Ycx/2zOTD 13 | AeDRqplwL2YociTSZ9cvWMKRJiBTbr7bIaDgTsUjqaAFlzMD00R+5zQeGvZFDWQT 14 | ug94J1dirQv9GsWKs+Erp6jK7HiL11TUvaYB6zNKBoVUN7JFAmxLU9ckPMhdclT9 15 | AGK3XKfln7l9J5turd3crZ6i2F6/3KYA5D51RrPdKjXCBZ0nAgMBAAGjUzBRMB0G 16 | A1UdDgQWBBQ6OUGRx/a+R+14PgZjbGII5el4ZjAfBgNVHSMEGDAWgBQ6OUGRx/a+ 17 | R+14PgZjbGII5el4ZjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IC 18 | AQAN3TbckTbaNDifzQRZck4YRCbgoIadbSofFX+B21IbQVAHH9eqz6eI0Xxgznlf 19 | AeGtdxl/MHhw26rhw/Y9x8SDRx7cVBAarfa9GZT2B6Ot2m1wP8mEJMxlVi3fL1uQ 20 | plPAewIl2Z5vSdmv+LICEFO8TjnzxOUbge/4XRFZm8S1TywmiVhysQ86tHXIJAiF 21 | UZdHLqccR13o9oeZ1dmBlb6y5nD/MicYIgOHuUItD2XeU/YcKfNz5nR31BgPXXuS 22 | ivs3hcARxnfV2F5+5s/y4r9mUGlUc8SBpHN2c9PMXtEqHHgbqvQ2kbKl0QCW3Ggn 23 | dlQ+NT2WMT2fQ2JTUOJuk+Oap72kS1etldqQ3y2VXStfV3yIcfRT4RscK8kL4yv+ 24 | bfttlfzpgru5qfq6npNoV4QYCSwkyEBvJPT/sgOdUMX3Bmq6rEw54kzFHvkGX7W6 25 | 8z6jz8oSPy+kqTYebmBPLNcEWVWGE72TCAotZJ6tBBijHefbUfcwQOGfXqqmqRSM 26 | 1+rb6fyNBWQPlwB0byspKoa3THZUk2sBZoylf+IlY7sopi4GqZ7rJ8R6NeX7MwCu 27 | 3JcxMhtn/shodKSLWsW7Ua/hEaQ1MFoqQuaS2MhXwfBzjc4LveJVvu9+WI8D9ol9 28 | WWsACCyGYAt7F6/Gv0Z2KbUe6YVoY2fxJinDocmIYw4yMg== 29 | -----BEGIN PRIVATE KEY----- 30 | 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 79 | IOzzRLCPWuLjE1QSqY8Ahqz1498n 80 | -----END PRIVATE KEY----- 81 | -------------------------------------------------------------------------------- /test/rsa-self-signed.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE/jCCAuagAwIBAgIJAPQJoxDs27dFMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV 3 | BAMMCWxvY2FsaG9zdDAeFw0xODA5MjYwNzI0MDRaFw0xOTA5MjYwNzI0MDRaMBQx 4 | EjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC 5 | ggIBAN5g4nK81o0N6OlCSz2nHNF+sq/f3SGu4AE8G6KxyeSR6AG5EL53UHhZsFpx 6 | 8dv2sUP8hIpTmymFgvklDnfidaosltrhcijXEP1LD72U4bVYohdf1n1FS24Y+tDO 7 | oKzDXLmwpikXWWEETCAIWlGX3706lYEJExobU7JqjFsNv/eSmfFBeSeMTbSnBr92 8 | gz/uTMiGZ2Xt16ddiCwzA++9ziUUFf/i35jI7wDRS4BrAZ0u1m8dcFxXQLNQuO9m 9 | /mE5m16vnlKy+daguebrBgZzgCb/MnF8c9LexENVI1SjilEdGEEnlTV5CSA70aI9 10 | YmwJ2i1HYRsGl1b50fa2bw60ljYkc/Z9loIELlj+jAdHYEzf+ExmkK/hqsHtwcP6 11 | Pb2E3pxpSX7sV0e4EcaeQ7N32MUrmtrP3NovkUSRjaChMLV9Swb6lKl8jqLfExeN 12 | SGt+EPQJhpo+wRqRxTN5UF1YoMrRQYza5L1GVmB83cgqVIHw1xox5N2Ycx/2zOTD 13 | AeDRqplwL2YociTSZ9cvWMKRJiBTbr7bIaDgTsUjqaAFlzMD00R+5zQeGvZFDWQT 14 | ug94J1dirQv9GsWKs+Erp6jK7HiL11TUvaYB6zNKBoVUN7JFAmxLU9ckPMhdclT9 15 | AGK3XKfln7l9J5turd3crZ6i2F6/3KYA5D51RrPdKjXCBZ0nAgMBAAGjUzBRMB0G 16 | A1UdDgQWBBQ6OUGRx/a+R+14PgZjbGII5el4ZjAfBgNVHSMEGDAWgBQ6OUGRx/a+ 17 | R+14PgZjbGII5el4ZjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IC 18 | AQAN3TbckTbaNDifzQRZck4YRCbgoIadbSofFX+B21IbQVAHH9eqz6eI0Xxgznlf 19 | AeGtdxl/MHhw26rhw/Y9x8SDRx7cVBAarfa9GZT2B6Ot2m1wP8mEJMxlVi3fL1uQ 20 | plPAewIl2Z5vSdmv+LICEFO8TjnzxOUbge/4XRFZm8S1TywmiVhysQ86tHXIJAiF 21 | UZdHLqccR13o9oeZ1dmBlb6y5nD/MicYIgOHuUItD2XeU/YcKfNz5nR31BgPXXuS 22 | ivs3hcARxnfV2F5+5s/y4r9mUGlUc8SBpHN2c9PMXtEqHHgbqvQ2kbKl0QCW3Ggn 23 | dlQ+NT2WMT2fQ2JTUOJuk+Oap72kS1etldqQ3y2VXStfV3yIcfRT4RscK8kL4yv+ 24 | bfttlfzpgru5qfq6npNoV4QYCSwkyEBvJPT/sgOdUMX3Bmq6rEw54kzFHvkGX7W6 25 | 8z6jz8oSPy+kqTYebmBPLNcEWVWGE72TCAotZJ6tBBijHefbUfcwQOGfXqqmqRSM 26 | 1+rb6fyNBWQPlwB0byspKoa3THZUk2sBZoylf+IlY7sopi4GqZ7rJ8R6NeX7MwCu 27 | 3JcxMhtn/shodKSLWsW7Ua/hEaQ1MFoqQuaS2MhXwfBzjc4LveJVvu9+WI8D9ol9 28 | WWsACCyGYAt7F6/Gv0Z2KbUe6YVoY2fxJinDocmIYw4yMg== 29 | -----END CERTIFICATE----- 30 | -----BEGIN PRIVATE KEY----- 31 | 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 80 | IOzzRLCPWuLjE1QSqY8Ahqz1498n 81 | -----END PRIVATE KEY----- 82 | -------------------------------------------------------------------------------- /test/expired.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFCTCCAvGgAwIBAgIUSnhBQvKQARJ6voe5ugkpuPIO+kQwDQYJKoZIhvcNAQEL 3 | BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTE5MDkxODIwMjIxOFoXDTE5MDkx 4 | OTIwMjIxOFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF 5 | AAOCAg8AMIICCgKCAgEAnEfPLFHI9c8yHPU79v34FHneHh19X6kxoVnADvrBHl5b 6 | ugX1n40/a5rwjPNiotqdgUjez5U2vLvrPfiQs4YB11xVnBo+SQqohhXaVoAqxSOf 7 | rZkvxUMqF2IIbER9jYYKxQQMyJhG9pCl1Ta68NQS9P40jrCvpiqG7RoydX3us20G 8 | V2xoLs2a3A9ulNHJdkb+7Ub+jFC5bhYc5QelS5plO82O0SkZqzuQOr3Sc7ihu1DV 9 | bNts9biNingyOVjtJ/uquY0bZZMT7lsVhipsEfXTSqozhqn7JPFcfbuwpg2sWRi9 10 | zpafdxgKR+1UYftklqV8eZOHukEewCTdY+NN3refGKbWrCyWIPhdZsHST3kly+QD 11 | i2TrxmjR7nkDZmeyMBln+z1V+llVzwAfgn5eF1wgHEwuadViEb4+aOWrQdk6/Fjm 12 | nmsKjyjwylTHcVwvoV3/j7dNMqD3ee7X3LonCiNQf23eOA//r8///MIZgz8xq/pd 13 | 4WiiaiVqrxd/muOTxzbObjW76x9XPJf4mOYMMh5Ttdwd4mu/AIl7tri14g7wTzac 14 | RAF+GJg60JG4vFwSDuZVGwK+aA2Sj2IWXXhyU4Kc1PGRb53Jm4n4iWy7RvB2ltED 15 | Z4YwvOA3oNPQjAIX47xmhBVL5CYDzRmnc0v0CAcdadtXFYrGnWJ/MajEleORb9kC 16 | AwEAAaNTMFEwHQYDVR0OBBYEFNV7XVV2EdvIPxBurD/fEArXB7BlMB8GA1UdIwQY 17 | MBaAFNV7XVV2EdvIPxBurD/fEArXB7BlMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI 18 | hvcNAQELBQADggIBAEHKC+F/kb3idFkTYqZOil5+g1DpWfAT2ReWOZfNA840rOIv 19 | mZdZHh5J2fDGeO3PcM4hgTJHKrJB3/9FHU/DF3Bxurp7ARUfTbNs2saTlj03OvJV 20 | 0WU1KNG0eepjiM0IxEyX2EBYwZFN7F/HOJZ5+EzTPr0y6OnFRt1Z4WsesB+U2ML6 21 | adR/xnhLxspoFck3qoXbdUNhClZajLAUJVRmOLqk9MJil0ZgNz1KHpqGsJmVgj6F 22 | op8eE8ndarFChDIXWgoumS63HLoLXIC2xYvIW6Ji0ntLdEN7CNZYQ+rqMeVgMjhR 23 | Lsjho52+G6Q/FkYu8u065iVASaq0G5DfLUIakGGmjw7Q1tM/+AYEvyJMCY9rz8Y6 24 | hbFzwPKjNbDJHyb9ZZtVZ1Dz2eKPDf8THTAh8OCdyJWDJyN50pd8iHQBUVEmH2tY 25 | hUZJl9ocLg6tZPy9JGqAGiogC6LpkCDN7gsfbE8DHAfItPpYwvq7FPNuA+AJTSGN 26 | xY7/O9DGyViU8HH7j4SNxNYzM52wAe+g3J1VFK/niJ0g+lHhfTYBnPYrVLQJtqVJ 27 | zAvFFEqmdZVV8dDfBdmzaVzF47Z/w9YWy5hJ76clhzqtf3JRkNxSs9Fi43qf3Z6U 28 | xVVrDoLGuXGZX4C/gaVuUH+UmIMwb7Hvyk8fqYDf8I2P1bgHD3IKsnUvYCYZ 29 | -----END CERTIFICATE----- 30 | -----BEGIN PRIVATE KEY----- 31 | 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 80 | DhQorcGew3rIvGhmGU/06LZt/0/lkA== 81 | -----END PRIVATE KEY----- 82 | -------------------------------------------------------------------------------- /test/localhost-new.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFCzCCAvOgAwIBAgIUb2rQ9mUzIXSj4cCUIH67RSV53YgwDQYJKoZIhvcNAQEL 3 | BQAwFTETMBEGA1UEAwwKbG9jYWxob3N0MjAeFw0xOTA5MTgyMTMwMDJaFw00NzAy 4 | MDMyMTMwMDJaMBUxEzARBgNVBAMMCmxvY2FsaG9zdDIwggIiMA0GCSqGSIb3DQEB 5 | AQUAA4ICDwAwggIKAoICAQDFL9hI92f1mxk4YNqFLDcbbI04Hjf7rOsTm/NEqQEz 6 | E1pH6tPT/Svvj5QghGXC47sNN+4CyHHWHT1z2DKCQx6COg66dguU2wG5670F+UHX 7 | DeQRn96u/57m0KSOvc55jNAAHkvqjCNalsyxolHrENvQSAtZMTYUYaaJoMjE8sNf 8 | Xj4rz2QyxiFlPhDh0TG2BCC9dNC7RLcLSbNJdJ9NVbbCX1IHU6r65oy9oMLbeX+u 9 | g7/vRqhvZyhBwbVpc/JlarKqVgRbTmMNxBiBFrTCiFvZ2xrSzX5sCcSFVtZNYhAc 10 | QjQ1+XUv2vB3GB3Twd737LgHEWnO6lcWv68Nn4fTyMXRDn10OBW2QgNDpX03oOo8 11 | HwoopNaD+U4sgGkZAZcquMNtLAqr+3tSmQfn3wAd1UEl0GpWpg1f3pY0fpbtcwKY 12 | 3X0TTthttW4CBlIKk9TMuwYrkxcC+eDIi+CcZhXtdBPHg89ob9XYCvn2h8ghOwyU 13 | HCVMMGN8LkbMXzzt5yETHZgD+npbRECvmjRe5IOIcIumhQ/CGIN/1XBsWbB7eTXW 14 | 58+eSoGsPXT4HrBUy/QhlCvBbck3e0ateZ44LQkbOeK0oXpwQgkgDTgIGCApkZbl 15 | +4suCT9dO3bYatwc4bRNvwxCjQn9MLDV1hn9w+YLPF9wGINoAXQCiAgowGOLyveJ 16 | EQIDAQABo1MwUTAdBgNVHQ4EFgQUtD/llnwENoqpUW9tttkWondntxswHwYDVR0j 17 | BBgwFoAUtD/llnwENoqpUW9tttkWondntxswDwYDVR0TAQH/BAUwAwEB/zANBgkq 18 | hkiG9w0BAQsFAAOCAgEAOs+Ad6t4XntubkJcOX5G3MSfy6reou5Vb6xfOqLWCpxo 19 | VN4Ih/BvtVfrwU3Ywts74QwOS8l1diu3ikXV+Nxtuui5sKputGpRN4TM2FEhLKc4 20 | EQvrTSrg3eOYQE3gARWasFAVo7vSKdtIUx6h5FboAmK8V6s6/S/wFRfwKxyK32X6 21 | I94VCClymhUDR4q9Y2QyBxizEo8Ym8wAc8ptpp/HVeEzMGBd/jcR7P8EkEghJ39g 22 | s86xUvNtyphEqYVR8qNHlDk44PWtijVmmE/UNBdtzs+ERntRL6ThEnXJ2eV1a610 23 | 6upjTAu3OpVh/nKtMDjvBZrjp9wghRhwhRQ2pHgxauMOzteVLATgUOaUhxZW5hTE 24 | dUO/6f55G1pk2iWfOQ+9W51gJfrVz5arvKYmC0z0PIG7Jt6g6FjCvki1aQFyo/xP 25 | IalL5Esj9clk1LcyWThVfLvz2utOnoji3Mu7yrM2vset6H2keBYc7TUE7TpG4hjY 26 | J20C/OaliJVutlIUWNHoEiSIPwdRI/+1atrixJtbTayWkTJYessd2sizpJakVPLy 27 | qdtk9yOSlM7mjrZDRIXIjne27JxLaoHruSHSKvtNVu2YUka4THsH7aoSrHE67Cf0 28 | Lkn5DZ1Sf2pBbofU2KF6xBGghxHmNYEbs9mqRb0zySb47HME1TGiL/5b5+50KxY= 29 | -----END CERTIFICATE----- 30 | -----BEGIN PRIVATE KEY----- 31 | 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 80 | /odGM8l9uEBxb+wk8tgjwRfV47S3ZA== 81 | -----END PRIVATE KEY----- 82 | -------------------------------------------------------------------------------- /test/localhost-old.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFCzCCAvOgAwIBAgIUa2wRSUt3QFngvnH+n03ErY19Sz8wDQYJKoZIhvcNAQEL 3 | BQAwFTETMBEGA1UEAwwKbG9jYWxob3N0MTAeFw0xOTA5MTgyMTI5NDZaFw00NDA1 4 | MDkyMTI5NDZaMBUxEzARBgNVBAMMCmxvY2FsaG9zdDEwggIiMA0GCSqGSIb3DQEB 5 | AQUAA4ICDwAwggIKAoICAQDMTqtfAb6pUGiULzxeftcYF79oeYKMWloB/8OxPxcd 6 | Djb21y7bL5mtc1aUKSktAKLkuAKVBcy5yVOm1TCPlj83Q5kgczgA/xdH2DIxtKuj 7 | VLkf1skSwdNTk8fVL2BDgLpO6Fc4OAQXGCk1X0fdv1z23j1C/9v3SZg6jrPs8OYV 8 | i1MVWLauFH9UemBv4uFn8spiKYrI0bgejPwLp5F0yRgQVZCdPoCyoIJ2ce8I9qez 9 | dr/p8UfT0ItoxOyCYxS6JG3rLwuVKN7S3p8JnGlKoGJvIdyO/+JsLjfyY0Y5eCWL 10 | SFtIQJ7oNzc1jRySAWNEo1VrqYsoV0HglREYupGJbl9WtWl4jc+1wwwJNCXDrpD1 11 | OA6SK5p6bZ6xtVys+Jl29UiSSxq4C5QCQBUA73EM8Zgx+NczT0XTEQTzaYNaAnWv 12 | +ovgPx3hGCvE7h7xS7C9E/x2/1Y34418UQLw64umDuDdDSMkc0qO4O/CUjhVSjQu 13 | KRtghG2QvmUApQlo4b1EIWWdw/JMKeQ5g8rlVs6iBv5m20n34MP3a60qlAx4x6dD 14 | PeXKQplmiqW7kRVwCrNNMO0USHbUqfNpNDH/8dMkYhaK31V1ABwGAk3/V+N97Dgc 15 | ibZrVa6nschswQhCLI6M75t0YKnwIcQK5sE7dY2UPcLnRSp5rFVA5oXA8NNwEv3b 16 | cwIDAQABo1MwUTAdBgNVHQ4EFgQUUgoEIEglHMpbbs6yHbvRTeUOg30wHwYDVR0j 17 | BBgwFoAUUgoEIEglHMpbbs6yHbvRTeUOg30wDwYDVR0TAQH/BAUwAwEB/zANBgkq 18 | hkiG9w0BAQsFAAOCAgEAegjS2NrwhDYfhoOlpT26673VsEyfbSR5dj0Is6vi7omx 19 | gyPDCY3r8Q89p3i8MVE9nXuHOsxVE7E8XKD0H48fGO6xT49C/BWpw3eWZe2fC/Rz 20 | axRgJJQk67h0WuRhGCHXBVMKrfbCvJtOjKFSiz4DKcagnjprn4tT33QmFUUivblL 21 | XMoGUJ9UY0uNoLtPG61F5mnXjEPbElgJGOtlzA9TP/f8aAnLi1/F2gOwiEk75KzS 22 | 7DW1/Xwu12CLroc21mUk0X+MBrDQX3wXgOMVyXpYhv6aX9CcXtCq0SlX2+wpVmPh 23 | N2xU7eTpRllL/MzIvABYrldXeePWETZjkyCQmt23VYoyDoK0OPtL52n5uKjphaYd 24 | RxGmxTrAXjZw9zAH753B+HD+f74DAFmNWV1GWlqCYBDKmRevV61/gSIvhF9vGc6g 25 | kIm1xIQZNKjwwRrTNgmL9YLPvZdTzp8qDDq6wyFvPxAK634CSXaS7I3KxqHmnC5N 26 | VSa3e8zIsN++Vm5z7aFLOOLcJK6Y1uX8fLd6CteBSq5pQ7LHxVH8x9rYhV6esMOS 27 | G5Gr5iMwhr6duQK5njtYaqtjHt3vBSGufvkPAWBOX3o6ibDolsenXu3nHScUP9Bk 28 | rQZvLzgjZcCkzBlrmHePctM8xw0NtmcG/lrM2ZD1Dav1+/krsebkQgsufflQTn4= 29 | -----END CERTIFICATE----- 30 | -----BEGIN PRIVATE KEY----- 31 | MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDMTqtfAb6pUGiU 32 | LzxeftcYF79oeYKMWloB/8OxPxcdDjb21y7bL5mtc1aUKSktAKLkuAKVBcy5yVOm 33 | 1TCPlj83Q5kgczgA/xdH2DIxtKujVLkf1skSwdNTk8fVL2BDgLpO6Fc4OAQXGCk1 34 | X0fdv1z23j1C/9v3SZg6jrPs8OYVi1MVWLauFH9UemBv4uFn8spiKYrI0bgejPwL 35 | p5F0yRgQVZCdPoCyoIJ2ce8I9qezdr/p8UfT0ItoxOyCYxS6JG3rLwuVKN7S3p8J 36 | nGlKoGJvIdyO/+JsLjfyY0Y5eCWLSFtIQJ7oNzc1jRySAWNEo1VrqYsoV0HglREY 37 | upGJbl9WtWl4jc+1wwwJNCXDrpD1OA6SK5p6bZ6xtVys+Jl29UiSSxq4C5QCQBUA 38 | 73EM8Zgx+NczT0XTEQTzaYNaAnWv+ovgPx3hGCvE7h7xS7C9E/x2/1Y34418UQLw 39 | 64umDuDdDSMkc0qO4O/CUjhVSjQuKRtghG2QvmUApQlo4b1EIWWdw/JMKeQ5g8rl 40 | Vs6iBv5m20n34MP3a60qlAx4x6dDPeXKQplmiqW7kRVwCrNNMO0USHbUqfNpNDH/ 41 | 8dMkYhaK31V1ABwGAk3/V+N97DgcibZrVa6nschswQhCLI6M75t0YKnwIcQK5sE7 42 | dY2UPcLnRSp5rFVA5oXA8NNwEv3bcwIDAQABAoICABElWQR0JQgYk248OJjFlE+V 43 | Tp7585bLzln0mPu7C7mIg9xbWrdSW1dPIqj0e1wnfYQsXSscfX3qA3cw1Q530X5V 44 | 0fgDM2QCBHYnEq9RNkZSyeZ+JAnK8m1FLbQacb24g+ozXX5+VPmLMYolIObfNHIT 45 | XJjO2Cr8piXj6/2qieSs+KAF/e7GEZmJW6aIJ9qNaaarTdZko0sNEen4eiCO79IP 46 | HD/WCv7ysYA3N5RM/u30vU5ozVpdWhXE0QRwREEK5Z6jKBmwgGK6BCbOVZfJtfkV 47 | omjt2JazmkZhRnd0OdJilCaQJcXfPGbmHMu0ChysiHKsm90a1ZB/chvdLHGYLa/2 48 | uSlsnnpyGIqfUwuzQ/mhbGY6T7n4AcxAHQ1izlbsgFMhAYIGrYwSITaZ2253CLOV 49 | yik4cyJko8dXxupOOetx2wWYXQLe6ScEUKSRHV78YGk/vmvlkDf8oPmiB+ARs3EP 50 | 3T7xywtCtYk7GPJEgnsfX3qUrQAwaMjMz1V2luch05ZsBUHJHli8WhmL7jFtSX7X 51 | 947owIWHSuivTFHIYxSkWPnv2X0WPqc0wzcNO3sh/H9qcvr4TC4GS/FKspiDL4L1 52 | laTcZ3BeoNYPcUrukZubd2mrhY+yaAW3vIr1JkCSQiaHcN6rpiIOUrjZITrWUapq 53 | MGLOC56q74tzjhRG/UxhAoIBAQD0WZFqOIij48JERB2+css32bHUKXSmlwANRkGw 54 | CyizCMeuf0o2AVn5hNwynZyW+8t+s5xb70DMSLJ1ZWHvZgrmBF2EdcHZerZ/DGgs 55 | McC4uXpUfonWBr11TtuvVG4MPprw3bHS7KnyNDPK5Pz+e6NcxXUtQEE8+GMa3OzT 56 | 9wTjgSTUh3ZLtlU9rGo3YnNo3dkM0QNIwbVbFcpdG6RXf1nbvMFGIndmBHkGb68R 57 | dBVdbOehec9aiJ2gPweleMcx388cXFNZmk0gaZG/Wh8RUTAuV/8Qj6NlWCv+n5ll 58 | YtYH3zbggjTv7KQ6bmi4EpT8Fp/8WZaYn1sd6Q0FPBvZWWENAoIBAQDWDFvKyU19 59 | mUGLQ/9iQywrfg/gwLICD4BqZpf5QVUD6crsE79fjbBL5w5wZZOdHdxmgjdRV+2x 60 | vKB8TAuSBtEHkWdjlqgXl6Vb6VkxR3+4MchX0hx1mLunV3IAAj7TXc0IzQsncTVI 61 | EBYKHCvqr2+3LQ32kW+mPjMn3RC5OxbObdzu36xsydNkVbBGuBTDOj2uJrcjq/Mh 62 | 4sfQriBeDltac5ZyKIINjjOsmyZsANSF4UvLLqpNCVBlf45skVMNNeONsnRn74iQ 63 | qTCzkCYTeefWqrRfOO6z9/lG3XTBKBjQJlPEsrX1G9YswLWEOdXCTRr63JH5iD/2 64 | fv0hsuRHuA5/AoIBAQCEeYbwCsRx7kpljnR9y2jH19ukhfE7XERn7f4w/mvLXOg9 65 | f79xNhxcvh5PxsmdxdeW0rPj1/vOhV9TbGrDSxR/jkBRTzKQwQnuKYDU7QhqXgyn 66 | gI8kHTVcQyuxi9m6A7sm+VquWybsLEckKi/LnKZ95oPXy61S6+t3Je20IYlh+qNI 67 | IkUNWUYvkKlpa5Pcd+J0I2Ffcb3sKw0ym4jm8yF5k4VJi+glxCkFR0mn8cKfiyqt 68 | jgfQMp8awPFZUfjU3l1AKAH8Yz6LYxDkqGyNi/HOPmEHzogsqrf3bPnSUNpvHCJ5 69 | Z75hkW+cSj22BkUFwxJr0vpltofE2JP5kZQbq2yFAoIBAFFZ6RktYPI3aR3Q/iMO 70 | fJ8bDlXRZtY5pgjKbUAigHk4RhNrffSHGVX/vxEgyfWQ/89F9lhIHuLKWgRjspIR 71 | XSiPLsg+3iv5SrOgAHImRnhvE0GtbXuUTN3Q8lbmg5I5uE97/p1C/Ykh8/5rrpVH 72 | kCICUahApjp8sVyZPF3qA0T5pFp1UEcYfetIVfLKy7lXTVH/DOPV2lYqpt69Dzgw 73 | JLA/9hH1K1iTjzguz2+E8UDg27mhRsB4pcGttSe2I+aFcLLN/Ef0VecFJqlYQ+Br 74 | GJ4RyvPV44Mq3+b7DADNiICz0GqVsWoy5jekFljm0kNCotptMl2wVv8MTJCeOPy5 75 | C2kCggEAS+VMeKzvlOGrKFHfhH36oPS1uv4BPBH51lEuf7qIOiHkcADX+0lvgLzu 76 | hmqjz6Ma6yyk95ER7G9TjrDFMY6wNl26cpS7KVd4FTBzBS/8AuXwlNfM6ADnna1H 77 | Y4QgnNmMkUBBhwyayLvavFZY+DFN+Fl0Fd4yhzLezLLH6bBQVHgn/Z35DA9+5dUF 78 | B9f9tXPWWsMh8l4ou+lU9QLP4ybtb+XpBZ34kPxxkaQXhFVcF1/cYTgpVVAgDwZn 79 | Tg95guvubH5UxR9opXoZORdbTmYnHyW4iO+oo4e7aE2GrV9D9DLa8VOD4JFF8rvi 80 | vmvI2I3rpECFavFmY+4WmIOrYcjoMw== 81 | -----END PRIVATE KEY----- 82 | -------------------------------------------------------------------------------- /test/text-between.pem: -------------------------------------------------------------------------------- 1 | foo bar baz 2 | 3 | -----BEGIN CERTIFICATE----- 4 | MIIE/jCCAuagAwIBAgIJAPQJoxDs27dFMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV 5 | BAMMCWxvY2FsaG9zdDAeFw0xODA5MjYwNzI0MDRaFw0xOTA5MjYwNzI0MDRaMBQx 6 | EjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC 7 | ggIBAN5g4nK81o0N6OlCSz2nHNF+sq/f3SGu4AE8G6KxyeSR6AG5EL53UHhZsFpx 8 | 8dv2sUP8hIpTmymFgvklDnfidaosltrhcijXEP1LD72U4bVYohdf1n1FS24Y+tDO 9 | oKzDXLmwpikXWWEETCAIWlGX3706lYEJExobU7JqjFsNv/eSmfFBeSeMTbSnBr92 10 | gz/uTMiGZ2Xt16ddiCwzA++9ziUUFf/i35jI7wDRS4BrAZ0u1m8dcFxXQLNQuO9m 11 | /mE5m16vnlKy+daguebrBgZzgCb/MnF8c9LexENVI1SjilEdGEEnlTV5CSA70aI9 12 | YmwJ2i1HYRsGl1b50fa2bw60ljYkc/Z9loIELlj+jAdHYEzf+ExmkK/hqsHtwcP6 13 | Pb2E3pxpSX7sV0e4EcaeQ7N32MUrmtrP3NovkUSRjaChMLV9Swb6lKl8jqLfExeN 14 | SGt+EPQJhpo+wRqRxTN5UF1YoMrRQYza5L1GVmB83cgqVIHw1xox5N2Ycx/2zOTD 15 | AeDRqplwL2YociTSZ9cvWMKRJiBTbr7bIaDgTsUjqaAFlzMD00R+5zQeGvZFDWQT 16 | ug94J1dirQv9GsWKs+Erp6jK7HiL11TUvaYB6zNKBoVUN7JFAmxLU9ckPMhdclT9 17 | AGK3XKfln7l9J5turd3crZ6i2F6/3KYA5D51RrPdKjXCBZ0nAgMBAAGjUzBRMB0G 18 | A1UdDgQWBBQ6OUGRx/a+R+14PgZjbGII5el4ZjAfBgNVHSMEGDAWgBQ6OUGRx/a+ 19 | R+14PgZjbGII5el4ZjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IC 20 | AQAN3TbckTbaNDifzQRZck4YRCbgoIadbSofFX+B21IbQVAHH9eqz6eI0Xxgznlf 21 | AeGtdxl/MHhw26rhw/Y9x8SDRx7cVBAarfa9GZT2B6Ot2m1wP8mEJMxlVi3fL1uQ 22 | plPAewIl2Z5vSdmv+LICEFO8TjnzxOUbge/4XRFZm8S1TywmiVhysQ86tHXIJAiF 23 | UZdHLqccR13o9oeZ1dmBlb6y5nD/MicYIgOHuUItD2XeU/YcKfNz5nR31BgPXXuS 24 | ivs3hcARxnfV2F5+5s/y4r9mUGlUc8SBpHN2c9PMXtEqHHgbqvQ2kbKl0QCW3Ggn 25 | dlQ+NT2WMT2fQ2JTUOJuk+Oap72kS1etldqQ3y2VXStfV3yIcfRT4RscK8kL4yv+ 26 | bfttlfzpgru5qfq6npNoV4QYCSwkyEBvJPT/sgOdUMX3Bmq6rEw54kzFHvkGX7W6 27 | 8z6jz8oSPy+kqTYebmBPLNcEWVWGE72TCAotZJ6tBBijHefbUfcwQOGfXqqmqRSM 28 | 1+rb6fyNBWQPlwB0byspKoa3THZUk2sBZoylf+IlY7sopi4GqZ7rJ8R6NeX7MwCu 29 | 3JcxMhtn/shodKSLWsW7Ua/hEaQ1MFoqQuaS2MhXwfBzjc4LveJVvu9+WI8D9ol9 30 | WWsACCyGYAt7F6/Gv0Z2KbUe6YVoY2fxJinDocmIYw4yMg== 31 | -----END CERTIFICATE----- 32 | foo bar 33 | -----BEGIN PRIVATE KEY----- 34 | MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQDeYOJyvNaNDejp 35 | Qks9pxzRfrKv390hruABPBuiscnkkegBuRC+d1B4WbBacfHb9rFD/ISKU5sphYL5 36 | JQ534nWqLJba4XIo1xD9Sw+9lOG1WKIXX9Z9RUtuGPrQzqCsw1y5sKYpF1lhBEwg 37 | CFpRl9+9OpWBCRMaG1OyaoxbDb/3kpnxQXknjE20pwa/doM/7kzIhmdl7denXYgs 38 | MwPvvc4lFBX/4t+YyO8A0UuAawGdLtZvHXBcV0CzULjvZv5hOZter55SsvnWoLnm 39 | 6wYGc4Am/zJxfHPS3sRDVSNUo4pRHRhBJ5U1eQkgO9GiPWJsCdotR2EbBpdW+dH2 40 | tm8OtJY2JHP2fZaCBC5Y/owHR2BM3/hMZpCv4arB7cHD+j29hN6caUl+7FdHuBHG 41 | nkOzd9jFK5raz9zaL5FEkY2goTC1fUsG+pSpfI6i3xMXjUhrfhD0CYaaPsEakcUz 42 | eVBdWKDK0UGM2uS9RlZgfN3IKlSB8NcaMeTdmHMf9szkwwHg0aqZcC9mKHIk0mfX 43 | L1jCkSYgU26+2yGg4E7FI6mgBZczA9NEfuc0Hhr2RQ1kE7oPeCdXYq0L/RrFirPh 44 | K6eoyux4i9dU1L2mAeszSgaFVDeyRQJsS1PXJDzIXXJU/QBit1yn5Z+5fSebbq3d 45 | 3K2eothev9ymAOQ+dUaz3So1wgWdJwIDAQABAoICADnK5A79lKTD4Kv+Vp+HNq4b 46 | R0T94PJICF9Jx8TYf6evY6RO/FMDnx4n8PUQd9K6ogcRMUDhyYlY8VjekIwHhpzb 47 | SblIrep/OiMZxtV2Q9JlXnrEpXGY+Kl8RHLPwGIlw4tepVA4iTn1a/NHOHHMDpGX 48 | EBOg0B4QzAeqOR/QyvsEVo2kfmAQzoEMh2xq2GFdBQd4JpcO4OTWIfWarIM5yMFK 49 | Hw2JYKLMVZDY085kAN9gtMnb1L+qzV3MtMNC+Qk7d218JqFHcjadPrMMkVxL2BpK 50 | aEmmFlPPISJ+ldgBSIkcDpmgN97VvdFqumh3m4SIavMpgcsMQ3iPJEoBz4r6065y 51 | apw0W3J/h5nyI62dpVu3jhIUBVeJvDkV0zRZrWLHqLRnlQ1sSC/3cYxcB1fZ+CYL 52 | kBTDmcpUsUipC48YslhtiTzgo6rDOXj2Sqpt/nLtfNiRuxOiT2NbuEcQiYziHvrJ 53 | UE+n4lCDaT+UA3eRxqKvtJZlFXJHn7OtJ7si6rJA9WfTsjeX6isiOxWUHQxFTxgS 54 | jmUUQ1zNa2rivHZwgqmoXTh6qCn0moqPaKIvl3I+aS+AjnkJSnlWmL6RXBGZWl+A 55 | ixrn5kGCnlj9/tKnWAE4WJ9C9B/0VdwPlszf1ot2WUCtAt1yzwfAB44lK3xSnY2G 56 | ab17OFfAYH665oCNobSBAoIBAQD1MKz+beAJnwy+353gymqwqxjCYBs8dQshAiXY 57 | N5AtZxDlXNtlEK0HrZ8seZzSMGI9swzxOVmYq4XRkgo4nC+R4nV48CL+MclVEdUn 58 | ma9hFxm/sKiblp2paWbwajFwFgYzVjnxNjZ5I8a5AQd+pEsnuzeIdiSeixZlKY9f 59 | TsyiFosb48YVD7TmMAFkN/5yYZ1PtzizHsrzqRGvnkIE1/NQOnqdi6G+ap1T3Hmt 60 | QTc+RTS3ATnql7oH6ouaz7uJzhQjD3n/ISIZB596uZt12QjFEORhvcSPGRFhw9LZ 61 | Fyh/a1hxkKfPdGR6B+43GFel+i594fa836jtPkL8YEm12hvfAoIBAQDoLr79qTDn 62 | h5R9RjUx37FpeRD6xjsyj35+pUBjpsGlsY/W/cIrr4N8yJ8A8dS7yF8GlW6Utc9+ 63 | EqLReZnzrlvV+t6OBQfF2ChNd7BpKAwi/dmWnfMenIO3APkDTGr7Pfp7XNHjR3il 64 | nF7i9bWmTehKgK26sKA/+qApdI3dTI2T3f6Pi4u47zKIM6Ejd9Ew/oJEq/xvyk4J 65 | YeDu8Z4EstTCSJKwpiyTU4gMcEKn+wqkAelJs9pfalr56s4E1uucFXs2PSlPCmW3 66 | HCh8kz8mPGJPVk0t9Ezk5PD6rT0j+tqNATKAJnrOlGTIaKOU2DsctFdl0rR7XOtB 67 | NJWAZPw356e5AoIBABjdRM3QaqXPIUXB+4quPD+KOkqL4HczD1vbkebpS4+vIgGA 68 | dyc6l7Fto/SoqISQL4Y+QBO+Ux2uVzW3b53qCNDsfCk1gPbyKY6c7lbDuQtJgmz0 69 | B0Uv8vEZJT1AJ59MPHi8R8f0TXXfcOmV9yKampx+2dTW1kPVqwG7QzTREuNlEdpU 70 | MOQ14YxuVdXJ7929lGxfEjrn5oDrJkX+8Ib5N01tgL21SUO8IBQ/CX/OW0HPVtcw 71 | IqVtmYnv+RRL+KKy6Uvc5+w9ee273ipd5CwBRGhnnCFlUyeHzUFy0FD/jjXNtvxd 72 | RQc+sGnHoBqqN0k3o7tUTOc+by0P4U8inJb6dVUCggEAHgkeD/Z5Kxpw7RvN+du0 73 | Oa69sZM/STUi6gM6pymFV4f20ZsWKUyVN+lEGH0wRfKPyGxAV+CFLQwAIBUZA1sE 74 | 6lN/wuOHs+JYpFzdZ10U5Nnt8fwQ3V7l8yCfFfwmwsWrx5WkWUB/rPzjkXyzuQXP 75 | DQREdSgwMtabLYG0cHJcxkoriipFMvFOmiwDpnDzkOD7vSJ6j4OeQLx2urJq/LSd 76 | rVxyDYQRtCVULje/h7eEEt9kbHJlx34cssPbTuj2pcRpogSbeWwg6GUuH590xd99 77 | 4EGLzmwSHnI3clZC2Iq1BxSmkclojZzIxNw0fSbTzszNmZB+ZI8Kp+7DgE6QCjNf 78 | kQKCAQBPzPty1WCmtCOoB0ovbeqQkw3IGr+dtiKd/YL5hBT//U/a9so2MPV4mD30 79 | EeyqpS2hgEs/49uhHN+gzgM+rYwlIzOEkzyST5sClRcCtEzyPxEE398cDyQaIl+E 80 | ofjVBLG6LbTZLCQ0aSYVfY+KksS20gI1dnHhK/RGMm7tZEuA68m/PHSNzLp/Ca9m 81 | WQimGkqX1+xTUu+jaFeZ+CrP8YshFJQxi22iXqHwaQCB901jNBDHeBCUYhQAsc9k 82 | 8ECq/0PHjpEO27QsMPE7v/tJbAo79yok3rm9SZ5wzs+yzbf8Sd92hCQU/Ml9bJfY 83 | IOzzRLCPWuLjE1QSqY8Ahqz1498n 84 | -----END PRIVATE KEY----- 85 | baz baz baz 86 | -------------------------------------------------------------------------------- /test/new.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 31 | -----END CERTIFICATE----- 32 | -----BEGIN PRIVATE KEY----- 33 | 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 82 | py29TAC+C4LjXVwOIxS1yKXaMf/H 83 | -----END PRIVATE KEY----- 84 | -------------------------------------------------------------------------------- /test/old.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 31 | -----END CERTIFICATE----- 32 | -----BEGIN PRIVATE KEY----- 33 | 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 82 | CZDam+bbRfGpEe4gmtnCCSyCC6hWlLc= 83 | -----END PRIVATE KEY----- 84 | -------------------------------------------------------------------------------- /test/no-domain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 31 | -----END CERTIFICATE----- 32 | -----BEGIN PRIVATE KEY----- 33 | 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 82 | Pur37h75zQYX8qkAdk4v/oEYweGx5+fZEQ== 83 | -----END PRIVATE KEY----- 84 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We'd love for you to contribute to our source code and to make our project even better than it is 4 | today! Here are the guidelines we'd like you to follow: 5 | 6 | * [Code of Conduct](#coc) 7 | * [Questions and Problems](#question) 8 | * [Issues and Bugs](#issue) 9 | * [Feature Requests](#feature) 10 | * [Issue Submission Guidelines](#submit) 11 | * [Pull Request Submission Guidelines](#submit-pr) 12 | * [Signing the CLA](#cla) 13 | 14 | ## Code of Conduct 15 | 16 | Help us keep our community open-minded and inclusive. Please read and follow our [Code of Conduct][coc]. 17 | 18 | ## Questions, Bugs, Features 19 | 20 | ### Got a Question or Problem? 21 | 22 | Do not open issues for general support questions as we want to keep GitHub issues for bug reports 23 | and feature requests. You've got much better chances of getting your question answered on dedicated 24 | support platforms, the best being [Stack Overflow][stackoverflow]. 25 | 26 | Stack Overflow is a much better place to ask questions since: 27 | 28 | - there are thousands of people willing to help on Stack Overflow 29 | - questions and answers stay available for public viewing so your question / answer might help 30 | someone else 31 | - Stack Overflow's voting system assures that the best answers are prominently visible. 32 | 33 | To save your and our time, we will systematically close all issues that are requests for general 34 | support and redirect people to the section you are reading right now. 35 | 36 | ### Found an Issue or Bug? 37 | 38 | If you find a bug in the source code, you can help us by submitting an issue to our 39 | [GitHub Repository][github]. Even better, you can submit a Pull Request with a fix. 40 | 41 | ### Missing a Feature? 42 | 43 | You can request a new feature by submitting an issue to our [GitHub Repository][github-issues]. 44 | 45 | If you would like to implement a new feature then consider what kind of change it is: 46 | 47 | * **Major Changes** that you wish to contribute to the project should be discussed first in an 48 | [GitHub issue][github-issues] that clearly outlines the changes and benefits of the feature. 49 | * **Small Changes** can directly be crafted and submitted to the [GitHub Repository][github] 50 | as a Pull Request. See the section about [Pull Request Submission Guidelines](#submit-pr). 51 | 52 | ## Issue Submission Guidelines 53 | 54 | Before you submit your issue search the archive, maybe your question was already answered. 55 | 56 | If your issue appears to be a bug, and hasn't been reported, open a new issue. Help us to maximize 57 | the effort we can spend fixing issues and adding new features, by not reporting duplicate issues. 58 | 59 | The "[new issue][github-new-issue]" form contains a number of prompts that you should fill out to 60 | make it easier to understand and categorize the issue. 61 | 62 | ## Pull Request Submission Guidelines 63 | 64 | By submitting a pull request for a code or doc contribution, you need to have the right 65 | to grant your contribution's copyright license to ProcessOne. Please check [ProcessOne CLA][cla] 66 | for details. 67 | 68 | Before you submit your pull request consider the following guidelines: 69 | 70 | * Search [GitHub][github-pr] for an open or closed Pull Request 71 | that relates to your submission. You don't want to duplicate effort. 72 | * Make your changes in a new git branch: 73 | 74 | ```shell 75 | git checkout -b my-fix-branch master 76 | ``` 77 | * Test your changes and, if relevant, expand the automated test suite. 78 | * Create your patch commit, including appropriate test cases. 79 | * If the changes affect public APIs, change or add relevant documentation. 80 | * Commit your changes using a descriptive commit message. 81 | 82 | ```shell 83 | git commit -a 84 | ``` 85 | Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files. 86 | 87 | * Push your branch to GitHub: 88 | 89 | ```shell 90 | git push origin my-fix-branch 91 | ``` 92 | 93 | * In GitHub, send a pull request to `master` branch. This will trigger the continuous integration and run the test. 94 | We will also notify you if you have not yet signed the [contribution agreement][cla]. 95 | 96 | * If you find that the continunous integration has failed, look into the logs to find out 97 | if your changes caused test failures, the commit message was malformed etc. If you find that the 98 | tests failed or times out for unrelated reasons, you can ping a team member so that the build can be 99 | restarted. 100 | 101 | * If we suggest changes, then: 102 | 103 | * Make the required updates. 104 | * Test your changes and test cases. 105 | * Commit your changes to your branch (e.g. `my-fix-branch`). 106 | * Push the changes to your GitHub repository (this will update your Pull Request). 107 | 108 | You can also amend the initial commits and force push them to the branch. 109 | 110 | ```shell 111 | git rebase master -i 112 | git push origin my-fix-branch -f 113 | ``` 114 | 115 | This is generally easier to follow, but separate commits are useful if the Pull Request contains 116 | iterations that might be interesting to see side-by-side. 117 | 118 | That's it! Thank you for your contribution! 119 | 120 | ## Signing the Contributor License Agreement (CLA) 121 | 122 | Upon submitting a Pull Request, we will ask you to sign our CLA if you haven't done 123 | so before. It's a quick process, we promise, and you will be able to do it all online 124 | 125 | You can read [ProcessOne Contribution License Agreement][cla] in PDF. 126 | 127 | This is part of the legal framework of the open-source ecosystem that adds some red tape, 128 | but protects both the contributor and the company / foundation behind the project. It also 129 | gives us the option to relicense the code with a more permissive license in the future. 130 | 131 | 132 | [coc]: https://github.com/processone/pkix/blob/master/CODE_OF_CONDUCT.md 133 | [stackoverflow]: https://stackoverflow.com/ 134 | [github]: https://github.com/processone/pkix 135 | [github-issues]: https://github.com/processone/pkix/issues 136 | [github-new-issue]: https://github.com/processone/pkix/issues/new 137 | [github-pr]: https://github.com/processone/pkix/pulls 138 | [cla]: https://www.process-one.net/resources/ejabberd-cla.pdf 139 | [license]: https://github.com/processone/pkix/blob/master/LICENSE 140 | -------------------------------------------------------------------------------- /test/gnutls-key.pem: -------------------------------------------------------------------------------- 1 | Public Key Info: 2 | Public Key Algorithm: RSA 3 | Key Security Level: High (4096 bits) 4 | 5 | modulus: 6 | 00:a2:4c:1d:10:17:dc:84:66:57:c0:10:bd:c9:a4:c3 7 | ac:23:85:10:c5:d8:92:25:bc:92:22:90:f8:10:f1:ca 8 | 4f:0b:9f:8c:2e:9d:91:20:78:35:50:4a:37:3f:31:0f 9 | c0:d6:85:d6:e2:ba:c1:c4:2e:d6:42:ca:eb:eb:10:45 10 | 38:e7:ba:7d:8d:82:47:31:b0:b2:fd:b8:20:f2:de:42 11 | 3b:73:c9:2f:26:07:93:8a:5d:b9:33:ba:52:9e:56:1e 12 | 69:a2:08:ba:b9:64:54:de:23:d0:d2:b5:a6:ec:0d:8a 13 | 2a:cc:6a:31:d3:88:ae:f4:04:57:88:fd:ab:c4:5f:21 14 | 8d:6b:99:25:43:b6:ef:2c:ff:c1:c7:9c:d4:0c:be:5a 15 | 68:ef:db:91:d7:e2:28:2d:55:ca:83:43:8f:b6:f1:36 16 | bf:e4:35:02:f2:fc:dc:d4:de:12:99:5c:36:74:b7:ad 17 | 2d:cf:7e:5e:78:ff:69:98:12:9f:9f:63:c4:38:9d:e4 18 | b3:6b:c8:96:72:30:d6:2f:05:8f:3d:83:b6:83:36:2b 19 | 75:99:fc:86:80:ec:88:c3:cb:b9:6e:ab:ca:19:24:5c 20 | 7a:9e:1c:ae:0b:fb:5a:90:be:2c:f5:12:37:84:11:01 21 | 24:62:75:c9:11:39:b0:a0:ff:75:fb:fc:da:e0:8e:18 22 | 56:92:c3:47:ae:13:6f:67:a7:36:0b:e1:ed:5a:42:da 23 | 0d:40:0e:8a:b1:65:db:ff:27:ee:42:61:b2:80:a2:1a 24 | 09:54:4a:e9:a0:52:7a:03:3e:6b:02:35:49:ef:d1:8b 25 | 15:b9:12:70:2b:bd:a8:69:37:30:de:df:70:a9:71:26 26 | da:95:9e:c1:91:68:b3:ee:a4:4a:ec:52:be:9a:c3:7b 27 | 1b:fe:bc:13:58:49:2b:01:25:90:49:27:9d:32:f7:22 28 | e2:14:50:df:6c:99:b1:3d:ce:45:d3:da:b0:99:e1:a0 29 | 5c:7e:80:ee:c6:d8:e6:55:5d:d5:74:a7:9b:80:17:1a 30 | 08:c0:89:c7:97:bd:fc:35:bf:9d:51:ee:82:d2:89:f9 31 | 48:dc:04:a4:95:ee:50:2d:ef:de:4c:50:3a:61:d7:54 32 | ad:63:40:78:79:54:3e:bd:29:91:c2:dc:47:33:9f:52 33 | c0:ee:b2:1b:5a:b4:25:0b:31:44:cc:15:6e:cd:8c:70 34 | 0b:28:a3:68:6c:4d:71:ad:ee:fb:af:dc:27:f8:23:9a 35 | f6:67:17:2f:c8:4e:55:47:74:13:37:62:45:f4:86:74 36 | 44:e2:94:9a:91:16:a5:12:0f:3c:a0:43:7a:00:4a:ee 37 | 29:37:1d:47:ff:f8:10:a0:d0:8c:77:b0:db:b6:02:95 38 | 8d: 39 | 40 | public exponent: 41 | 01:00:01: 42 | 43 | private exponent: 44 | 0b:fe:49:eb:85:87:e3:34:d8:e9:35:93:80:d2:2f:2f 45 | 8a:54:6b:ff:e2:d8:31:f7:b4:68:df:83:a5:64:e8:c9 46 | 5c:97:86:b9:6f:25:69:5b:d8:bc:bd:2f:45:f9:2f:2b 47 | d5:ee:80:09:b3:39:5c:71:05:9e:5b:f6:81:21:6b:ac 48 | 1f:db:b5:29:7e:e6:19:f2:ef:d7:36:03:21:4f:71:5f 49 | 89:57:e5:a7:0e:ca:45:60:55:ae:4f:e7:89:11:de:1f 50 | 7a:3c:e0:e7:90:55:81:30:ee:5e:93:d3:e8:ff:bb:25 51 | 1c:84:00:78:9c:52:29:70:24:b3:d7:e3:18:98:db:16 52 | e3:4b:a6:e8:51:1d:6d:9a:dc:cc:0e:77:d1:15:44:03 53 | 1b:2e:83:a1:8b:71:e2:9f:c3:5e:5d:e0:c1:e9:23:5d 54 | 9c:d4:9a:0a:30:cb:74:e3:90:46:4a:fd:2f:34:df:94 55 | 52:10:09:95:31:72:b1:2d:37:24:85:2f:db:eb:37:1a 56 | 57:2d:b4:88:6d:b9:76:32:17:fa:b8:c0:05:04:3c:c3 57 | ed:04:2f:40:59:ba:65:95:a9:77:57:34:91:15:11:cc 58 | 71:e0:b6:af:e5:fc:34:54:09:68:76:d7:29:68:44:48 59 | 57:0a:53:38:75:a5:ea:cf:d5:fd:30:1b:50:9a:cf:8b 60 | 80:12:35:7c:25:0c:d0:92:99:17:27:e1:12:82:5b:a1 61 | fc:75:77:2f:23:68:90:72:9c:2e:28:30:88:49:8c:c8 62 | ed:03:8e:55:b3:34:ae:ac:7d:9b:7e:f5:6e:c7:66:30 63 | 82:a5:45:72:dc:2a:13:10:22:b4:e1:dd:20:b6:a5:6e 64 | db:fd:0a:84:77:e8:dc:7e:ec:4d:00:54:99:2c:34:1c 65 | a1:85:e2:9b:bf:8c:f2:76:70:71:3c:2f:af:f3:0a:af 66 | 6e:e3:32:85:93:73:a4:be:06:5f:ea:9b:70:58:9e:73 67 | b2:d8:6b:64:14:d5:fd:65:96:16:0e:49:ba:37:4d:05 68 | 27:89:4d:8a:d0:3e:65:93:2a:71:f4:6a:e9:d3:26:2d 69 | a8:ab:34:8c:5c:50:e3:9f:48:46:bf:94:e4:8e:31:fc 70 | 02:df:ee:5d:9a:20:8c:17:a4:07:c6:43:5d:f2:9d:53 71 | 21:db:c9:a0:ce:d4:64:79:ef:e0:eb:17:9f:46:5c:d4 72 | f2:51:fe:60:fa:00:f6:c5:78:7e:43:f7:44:34:11:fa 73 | b9:a8:b3:fb:88:81:e2:47:de:2e:b0:59:39:b0:da:55 74 | 11:9f:8f:7e:db:1f:68:3b:46:c4:40:31:19:11:34:1b 75 | cb:08:5d:5f:e4:ee:37:0d:73:09:02:2d:dc:01:e3:95 76 | 77 | 78 | prime1: 79 | 00:d7:c7:9c:76:84:d5:43:27:0c:64:13:ec:db:18:7b 80 | dd:84:6e:45:1a:9e:ed:ea:31:38:bb:d1:c3:2f:24:57 81 | f2:7e:b9:23:09:74:1f:74:b6:2f:67:16:5e:8f:bf:57 82 | a6:4c:37:7d:b2:2d:3e:00:39:11:26:f5:67:a4:51:ce 83 | 27:66:67:46:6d:60:11:fd:00:b6:e5:a5:dd:2a:55:ea 84 | 91:f4:a5:b0:27:f1:f0:a2:e4:d0:18:35:ca:64:95:e4 85 | 34:81:9a:e5:dc:c4:de:84:aa:ca:29:97:63:0d:ba:f6 86 | 7d:90:6f:aa:70:ac:8f:ed:d7:4e:65:c4:bd:3d:44:07 87 | 0e:27:88:0c:3d:6e:b5:bf:de:12:ab:ce:5d:09:00:33 88 | f0:4e:44:54:ab:49:ef:ac:3e:45:08:96:3d:9c:ce:46 89 | 67:b3:e8:b8:e9:dd:df:a7:b1:62:3d:4f:93:f0:25:8d 90 | a0:39:b7:d2:e7:99:65:e8:eb:5d:f0:b5:0c:67:2e:60 91 | 61:0b:5d:ac:e8:c7:64:08:4a:95:1e:12:a9:2b:1f:08 92 | 5c:07:10:54:a7:cc:6a:8e:bc:3f:6c:3b:90:0d:5d:df 93 | 5a:47:35:de:0c:bb:dc:ea:22:03:06:c8:b8:b3:bc:30 94 | ee:d3:54:c5:b4:af:40:93:cb:90:7f:ee:32:80:8b:4c 95 | 0b: 96 | 97 | prime2: 98 | 00:c0:8c:79:ab:2f:9d:7e:21:08:6b:70:be:c5:7f:c8 99 | 26:f4:0e:73:55:69:39:ad:d3:34:fb:a0:8c:9a:97:8c 100 | c8:07:eb:5a:cb:6f:7c:8d:4e:a7:55:bd:21:f0:c1:db 101 | 0e:f6:fd:4d:11:d2:1f:b5:0b:fc:3f:a0:c5:61:be:a8 102 | 7d:d3:9d:af:64:64:f6:13:44:ea:c5:e3:81:86:60:12 103 | 00:69:6b:53:82:43:d8:34:f2:f2:36:b9:d9:5a:86:87 104 | c2:0d:6a:ff:7e:7e:a6:6a:9b:41:d6:ee:2e:0b:a5:53 105 | 88:b3:2c:e6:6e:5e:f9:33:45:02:3d:8a:a8:1b:20:14 106 | 10:9c:5d:99:17:ed:d7:f0:b2:8c:55:85:8a:27:bb:24 107 | 4a:d1:78:c8:09:77:6e:d5:bb:54:8b:92:0b:5a:0a:e0 108 | 97:26:3a:3d:64:32:70:4d:f7:77:2d:78:3c:6c:71:67 109 | 5e:2c:56:de:1d:f9:db:73:57:b2:5f:f3:4e:58:ba:fb 110 | aa:cb:21:a5:27:d8:33:59:14:06:cd:2d:5f:49:97:32 111 | 32:93:dc:0e:f8:4e:e2:97:a4:16:0c:e1:78:00:86:ef 112 | 6a:a2:bc:b6:ff:61:29:fb:10:bf:33:83:e1:a2:b3:fe 113 | fc:60:b5:82:c1:91:8a:c0:a7:a1:13:de:d7:9c:22:0b 114 | c7: 115 | 116 | coefficient: 117 | 00:a8:42:d7:7d:71:28:9f:eb:56:87:e0:5d:90:14:e7 118 | ac:f8:ad:61:fa:79:f8:b4:8a:30:56:05:f6:13:18:19 119 | bf:2d:76:e0:43:a7:5a:d6:65:ac:ab:c9:4f:6c:46:2e 120 | 5b:ad:11:38:16:4a:e8:61:c6:ac:e6:bf:3b:c1:93:ec 121 | e1:88:8c:98:54:f1:f9:6d:2d:3c:85:88:ac:19:f8:fd 122 | 56:19:12:44:b7:35:be:7a:49:5c:fd:38:12:b2:64:d5 123 | 16:4c:1b:13:61:1e:a5:43:cb:b1:f3:9e:c9:a2:cc:9f 124 | fd:0c:1c:1b:5f:fd:b1:a6:f0:22:ff:23:f9:57:dc:95 125 | 6b:21:34:3f:89:01:40:cf:37:de:c0:29:7a:ac:2c:0b 126 | 3b:b5:6a:eb:f4:35:61:8a:49:a2:af:24:a8:94:d7:e4 127 | f7:59:56:ef:d5:40:4b:6e:9b:dd:d9:c4:e2:69:04:a6 128 | 49:67:4d:56:5e:b6:8c:d6:7c:5e:45:67:5f:d5:5a:b9 129 | 42:f1:db:e5:8b:f5:b7:43:fa:7d:18:3e:6d:4c:79:33 130 | 9b:5e:ef:09:85:fb:37:c5:2b:b0:69:d4:cc:ee:df:d2 131 | e8:e5:c6:44:6d:fd:56:77:bd:18:49:ba:2d:dc:ce:fe 132 | c1:86:3a:49:83:68:8f:33:d6:cf:6e:1c:b5:ad:e8:9e 133 | ff: 134 | 135 | exp1: 136 | 00:93:f5:31:9a:2b:17:38:c6:ab:be:6b:d3:3b:a0:9c 137 | 3e:96:b2:2d:fc:45:02:0a:55:d9:fb:1f:a8:60:c8:e0 138 | ed:4c:6b:92:54:c7:25:52:98:22:04:e3:d7:cf:65:7a 139 | aa:73:14:91:c1:fd:37:c8:5b:59:21:87:a0:9b:91:e4 140 | 74:2b:c9:a1:07:1a:9a:fb:0a:e4:5e:af:ce:e9:5a:a4 141 | 4f:94:9b:90:20:b3:cb:37:47:02:51:b7:80:a2:5c:0d 142 | 70:db:75:e5:2b:ee:6a:4e:39:f6:09:72:de:de:e9:56 143 | 5f:5f:0c:d2:a8:14:0b:55:21:0f:86:c4:d2:a6:11:e1 144 | 6c:99:3a:b6:79:e1:52:0a:d0:31:a9:51:05:86:48:77 145 | 54:cd:b6:19:00:a2:e0:0f:11:5b:b0:b9:a4:61:c3:27 146 | 8f:b0:53:61:88:2b:35:52:fd:54:c9:89:a9:7d:3f:a2 147 | cf:3d:04:89:3f:79:75:e1:b2:92:65:9d:1f:76:18:dc 148 | c9:6e:4b:bb:66:9a:62:43:35:30:ee:c0:08:f3:a2:81 149 | ce:e8:a1:10:0b:47:25:c1:dd:de:f7:4e:18:46:b2:19 150 | 97:03:c1:32:13:35:36:13:ed:62:79:e2:64:94:09:f4 151 | db:ae:59:30:7f:e6:b3:b7:24:1c:5f:3c:6b:b0:ac:d3 152 | 65: 153 | 154 | exp2: 155 | 68:dc:57:ea:75:7c:f4:d2:b2:8e:28:91:8c:67:61:ae 156 | 6d:1d:6d:8c:27:49:25:34:2f:d7:95:80:dd:34:09:94 157 | 6a:53:af:57:b9:38:80:90:44:2b:3a:5c:40:8c:a2:6b 158 | 0c:f1:b7:c6:2b:c3:de:ca:4f:2e:ab:b4:b2:dd:ad:4f 159 | 11:2e:ff:f4:d1:d2:fd:7d:7b:ca:17:73:53:a6:00:47 160 | bf:21:97:12:0b:36:b4:e7:56:27:9d:a1:14:5e:cd:92 161 | 4d:d2:66:a0:12:03:39:6d:a2:36:95:f1:f7:9f:aa:d5 162 | 78:96:cc:4b:71:67:8f:97:56:78:03:8a:37:73:89:d9 163 | dd:fe:4f:40:ab:e3:fd:0e:8f:c4:87:7c:4c:d4:eb:17 164 | df:ea:42:2e:7d:9f:9b:c0:70:73:ae:2e:86:74:04:09 165 | ec:e4:ac:c8:63:7b:ab:ca:48:5a:37:ef:d8:33:0e:53 166 | 48:1c:6a:cc:b2:64:e3:f4:ef:8c:b7:a9:84:43:18:89 167 | d4:24:57:98:52:72:ec:61:2e:af:10:b5:a3:13:37:60 168 | 3a:0c:0f:0f:05:8e:fb:7c:7e:11:b7:bc:ad:b4:ce:b0 169 | 29:3e:52:fe:d3:80:94:5d:73:73:6f:b0:32:f6:89:b8 170 | 12:46:e7:95:d9:58:52:41:38:44:e5:08:50:dc:6d:c3 171 | 172 | 173 | 174 | Public Key ID: 175 | sha256:5B:6C:AA:B8:06:53:8F:79:4F:B5:F7:E7:8B:83:95:6C:D5:39:6A:EE:CF:FD:7B:0B:36:78:0F:F4:F2:5B:61:1D 176 | sha1:10:09:CC:9B:FD:6A:A0:B4:B3:D3:4A:1F:D6:5B:D3:A0:5E:33:6F:AE 177 | Public key's random art: 178 | +--[ RSA 4096]----+ 179 | | o.... | 180 | | o .. | 181 | | +. | 182 | | o .. | 183 | | .S | 184 | | . .. ..o | 185 | | ..+o.o.* . | 186 | | .=o.oo+ =. | 187 | | o=..o E+o | 188 | +-----------------+ 189 | 190 | -----BEGIN RSA PRIVATE KEY----- 191 | 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 240 | -----END RSA PRIVATE KEY----- 241 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /test/pkix_test.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% Created : 26 Sep 2018 by Evgeny Khramtsov 3 | %%% 4 | %%% Copyright (C) 2002-2024 ProcessOne, SARL. All Rights Reserved. 5 | %%% 6 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 7 | %%% you may not use this file except in compliance with the License. 8 | %%% You may obtain a copy of the License at 9 | %%% 10 | %%% http://www.apache.org/licenses/LICENSE-2.0 11 | %%% 12 | %%% Unless required by applicable law or agreed to in writing, software 13 | %%% distributed under the License is distributed on an "AS IS" BASIS, 14 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | %%% See the License for the specific language governing permissions and 16 | %%% limitations under the License. 17 | %%% 18 | %%%------------------------------------------------------------------- 19 | -module(pkix_test). 20 | -include_lib("eunit/include/eunit.hrl"). 21 | 22 | -define(DSA_SELF_SIGNED, path("dsa-self-signed.pem")). 23 | -define(RSA_SELF_SIGNED, path("rsa-self-signed.pem")). 24 | -define(EC_SELF_SIGNED, path("ec-self-signed.pem")). 25 | 26 | %%%=================================================================== 27 | %%% Tests 28 | %%%=================================================================== 29 | start_test() -> 30 | ?assertEqual(ok, pkix:start()). 31 | 32 | get_certfiles_test() -> 33 | ?assertEqual([], pkix:get_certfiles()). 34 | 35 | non_existent_domain_certfile_test() -> 36 | ?assertEqual(error, pkix:get_certfile(<<"foo">>)), 37 | ?assertEqual(error, pkix:get_certfile(<<"bar.baz">>)). 38 | 39 | commit_empty_test() -> 40 | commit_empty(). 41 | 42 | is_pem_file_test() -> 43 | {ok, Files} = file:list_dir(test_dir()), 44 | {Good, Bad} = lists:partition( 45 | fun(Path) -> 46 | case unicode:characters_to_list( 47 | filename:basename(Path)) of 48 | "ca.pem" -> true; 49 | "ec-self-signed.pem" -> true; 50 | "no-domain.pem" -> true; 51 | "prime256v1-cert.pem" -> true; 52 | "prime256v1-key.pem" -> true; 53 | "dsa-cert.pem" -> true; 54 | "dsa-key.pem" -> true; 55 | "rsa-cert.pem" -> true; 56 | "rsa-key.pem" -> true; 57 | "dsa-self-signed.pem" -> true; 58 | "rsa-self-signed.pem" -> true; 59 | "secp384r1-cert.pem" -> true; 60 | "secp384r1-key.pem" -> true; 61 | "text-between.pem" -> true; 62 | "valid-cert.pem" -> true; 63 | "old.pem" -> true; 64 | "new.pem" -> true; 65 | "gnutls-ca1.pem" -> true; 66 | "gnutls-ca2.pem" -> true; 67 | "gnutls-cert.pem" -> true; 68 | "gnutls-key.pem" -> true; 69 | "expired.pem" -> true; 70 | "localhost-old.pem" -> true; 71 | "localhost-new.pem" -> true; 72 | _ -> false 73 | end 74 | end, Files), 75 | lists:foreach( 76 | fun(File) -> 77 | ?assertEqual(true, pkix:is_pem_file(path(File))) 78 | end, Good), 79 | lists:foreach( 80 | fun(File) -> 81 | ?assertMatch({false, _}, pkix:is_pem_file(path(File))) 82 | end, Bad). 83 | 84 | add_del_dsa_key_test() -> 85 | File = path("dsa-key.pem"), 86 | ?assertEqual(ok, pkix:add_file(File)), 87 | ?assertEqual(ok, pkix:del_file(File)). 88 | 89 | add_del_dsa_cert_test() -> 90 | File = path("dsa-cert.pem"), 91 | ?assertEqual(ok, pkix:add_file(File)), 92 | ?assertEqual(ok, pkix:del_file(File)). 93 | 94 | add_del_rsa_key_test() -> 95 | File = path("rsa-key.pem"), 96 | ?assertEqual(ok, pkix:add_file(File)), 97 | ?assertEqual(ok, pkix:del_file(File)). 98 | 99 | add_del_rsa_cert_test() -> 100 | File = path("rsa-cert.pem"), 101 | ?assertEqual(ok, pkix:add_file(File)), 102 | ?assertEqual(ok, pkix:del_file(File)). 103 | 104 | add_del_ec_secp384r1_key_test() -> 105 | File = path("secp384r1-key.pem"), 106 | ?assertEqual(ok, pkix:add_file(File)), 107 | ?assertEqual(ok, pkix:del_file(File)). 108 | 109 | add_del_ec_prime256v1_key_test() -> 110 | File = path("prime256v1-key.pem"), 111 | ?assertEqual(ok, pkix:add_file(File)), 112 | ?assertEqual(ok, pkix:del_file(File)). 113 | 114 | del_non_existent_test() -> 115 | ?assertEqual(ok, pkix:del_file(path("foo.pem"))). 116 | 117 | add_non_existent_test() -> 118 | ?assertEqual({error, enoent}, pkix:add_file(path("foo.pem"))). 119 | 120 | add_del_cert_with_key_test() -> 121 | ?assertEqual(ok, pkix:add_file(?RSA_SELF_SIGNED)), 122 | ?assertEqual(ok, pkix:del_file(?RSA_SELF_SIGNED)). 123 | 124 | add_empty_file_test() -> 125 | File = path("empty.pem"), 126 | ?assertMatch({error, {bad_cert, _, empty}}, pkix:add_file(File)). 127 | 128 | add_file_without_pems_test() -> 129 | File = path("pkix_test.erl"), 130 | ?assertMatch({error, {bad_cert, _, empty}}, pkix:add_file(File)). 131 | 132 | unsupported_pem_1_test() -> 133 | File = path("dhparam.pem"), 134 | ?assertMatch({error, {bad_cert, _, empty}}, pkix:add_file(File)). 135 | 136 | unsupported_pem_2_test() -> 137 | File = path("unsupported.pem"), 138 | ?assertMatch({error, {bad_cert, _, empty}}, pkix:add_file(File)). 139 | 140 | ignore_text_between_pems_test() -> 141 | File = path("text-between.pem"), 142 | ?assertEqual(ok, pkix:add_file(File)), 143 | ?assertEqual(ok, pkix:del_file(File)). 144 | 145 | unexpected_eof_test() -> 146 | File = path("unexpected-eof.pem"), 147 | ?assertMatch({error, {bad_cert, _, unexpected_eof}}, pkix:add_file(File)). 148 | 149 | nested_pem_test() -> 150 | File = path("nested.pem"), 151 | ?assertMatch({error, {bad_cert, _, nested_pem}}, pkix:add_file(File)). 152 | 153 | bad_pem_test() -> 154 | File = path("bad-pem.pem"), 155 | ?assertMatch({error, {bad_cert, _, bad_pem}}, pkix:add_file(File)). 156 | 157 | bad_der_test() -> 158 | File = path("bad-der.pem"), 159 | ?assertMatch({error, {bad_cert, _, bad_der}}, pkix:add_file(File)). 160 | 161 | encrypted_key_test() -> 162 | File = path("encrypted-rsa-key.pem"), 163 | ?assertMatch({error, {bad_cert, _, encrypted}}, pkix:add_file(File)). 164 | 165 | commit_self_signed_no_validate_test() -> 166 | ?assertEqual(ok, pkix:add_file(?DSA_SELF_SIGNED)), 167 | ?assertEqual(ok, pkix:add_file(?RSA_SELF_SIGNED)), 168 | ?assertEqual(ok, pkix:add_file(?EC_SELF_SIGNED)), 169 | ?assertEqual({ok, [], [], undefined}, 170 | pkix:commit(test_dir(), [{validate, false}])), 171 | {EC, RSA, DSA} = pkix:get_certfile(<<"localhost">>), 172 | ?assertEqual(true, filelib:is_regular(EC)), 173 | ?assertEqual(true, filelib:is_regular(RSA)), 174 | ?assertEqual(true, filelib:is_regular(DSA)), 175 | ?assertEqual(ok, pkix:del_file(?DSA_SELF_SIGNED)), 176 | ?assertEqual(ok, pkix:del_file(?RSA_SELF_SIGNED)), 177 | ?assertEqual(ok, pkix:del_file(?EC_SELF_SIGNED)), 178 | commit_empty(). 179 | 180 | commit_self_signed_soft_validate_test() -> 181 | ?assertEqual(ok, pkix:add_file(?DSA_SELF_SIGNED)), 182 | ?assertEqual(ok, pkix:add_file(?RSA_SELF_SIGNED)), 183 | ?assertEqual(ok, pkix:add_file(?EC_SELF_SIGNED)), 184 | ?assertMatch({ok, [], [{_, {invalid_cert, _, selfsigned_peer}}, 185 | {_, {invalid_cert, _, selfsigned_peer}}, 186 | {_, {invalid_cert, _, selfsigned_peer}}], 187 | undefined}, 188 | pkix:commit(test_dir(), [])), 189 | {EC, RSA, DSA} = pkix:get_certfile(<<"localhost">>), 190 | ?assertEqual(true, filelib:is_regular(EC)), 191 | ?assertEqual(true, filelib:is_regular(RSA)), 192 | ?assertEqual(true, filelib:is_regular(DSA)), 193 | ?assertEqual(ok, pkix:del_file(?DSA_SELF_SIGNED)), 194 | ?assertEqual(ok, pkix:del_file(?RSA_SELF_SIGNED)), 195 | ?assertEqual(ok, pkix:del_file(?EC_SELF_SIGNED)), 196 | commit_empty(). 197 | 198 | commit_self_signed_hard_validate_test() -> 199 | ?assertEqual(ok, pkix:add_file(?DSA_SELF_SIGNED)), 200 | ?assertEqual(ok, pkix:add_file(?RSA_SELF_SIGNED)), 201 | ?assertEqual(ok, pkix:add_file(?EC_SELF_SIGNED)), 202 | ?assertMatch({ok, [{_, {invalid_cert, _, selfsigned_peer}}, 203 | {_, {invalid_cert, _, selfsigned_peer}}, 204 | {_, {invalid_cert, _, selfsigned_peer}}], 205 | [], undefined}, 206 | pkix:commit(test_dir(), [{validate, hard}])), 207 | ?assertEqual(error, pkix:get_certfile()). 208 | 209 | missing_priv_key_test() -> 210 | Files = [path("rsa-cert.pem"), 211 | path("secp384r1-cert.pem"), 212 | path("prime256v1-cert.pem"), 213 | path("dsa-cert.pem")], 214 | lists:foreach( 215 | fun(F) -> ?assertEqual(ok, pkix:add_file(F)) end, 216 | Files), 217 | ?assertMatch({ok, [{_, {bad_cert, _, missing_priv_key}}, 218 | {_, {bad_cert, _, missing_priv_key}}, 219 | {_, {bad_cert, _, missing_priv_key}}, 220 | {_, {bad_cert, _, missing_priv_key}}], 221 | [], undefined}, 222 | pkix:commit(test_dir())), 223 | ?assertEqual(error, pkix:get_certfile()). 224 | 225 | unused_priv_key_test() -> 226 | File = path("rsa-key.pem"), 227 | ?assertEqual(ok, pkix:add_file(File)), 228 | ?assertMatch({ok, [], 229 | [{_, {invalid_cert, _, unused_priv_key}}], 230 | undefined}, 231 | pkix:commit(test_dir())), 232 | ?assertEqual(error, pkix:get_certfile()). 233 | 234 | commit_valid_test() -> 235 | File = path("valid-cert.pem"), 236 | ?assertEqual(ok, pkix:add_file(File)), 237 | ?assertEqual({ok, [], [], undefined}, 238 | pkix:commit(test_dir(), [{cafile, path("ca.pem")}])), 239 | {undefined, RSA, undefined} = pkix:get_certfile(<<"localhost">>), 240 | {undefined, RSA, undefined} = pkix:get_certfile(<<"foo.localhost">>), 241 | ?assertEqual(true, filelib:is_regular(RSA)), 242 | ?assertEqual(ok, pkix:del_file(File)), 243 | commit_empty(). 244 | 245 | commit_valid_chain_test() -> 246 | File = path("valid-cert.pem"), 247 | CAFile = path("ca.pem"), 248 | ?assertEqual(ok, pkix:add_file(File)), 249 | ?assertEqual(ok, pkix:add_file(CAFile)), 250 | ?assertEqual({ok, [], [], undefined}, 251 | pkix:commit(test_dir(), [{cafile, CAFile}])), 252 | {undefined, RSA, undefined} = pkix:get_certfile(<<"localhost">>), 253 | {undefined, RSA, undefined} = pkix:get_certfile(<<"foo.localhost">>), 254 | ?assertEqual(true, filelib:is_regular(RSA)), 255 | ?assertEqual(ok, pkix:del_file(File)), 256 | ?assertEqual(ok, pkix:del_file(CAFile)), 257 | commit_empty(). 258 | 259 | non_existent_cafile_test() -> 260 | ?assertEqual(ok, pkix:add_file(?DSA_SELF_SIGNED)), 261 | ?assertEqual(ok, pkix:add_file(?RSA_SELF_SIGNED)), 262 | ?assertEqual(ok, pkix:add_file(?EC_SELF_SIGNED)), 263 | CAFile = path("foo"), 264 | ?assertMatch({ok, _, _, {CAFile, enoent}}, 265 | pkix:commit(test_dir(), [{cafile, CAFile}])), 266 | {EC, RSA, DSA} = pkix:get_certfile(<<"localhost">>), 267 | ?assertEqual(true, filelib:is_regular(EC)), 268 | ?assertEqual(true, filelib:is_regular(RSA)), 269 | ?assertEqual(true, filelib:is_regular(DSA)), 270 | ?assertEqual(ok, pkix:del_file(?DSA_SELF_SIGNED)), 271 | ?assertEqual(ok, pkix:del_file(?RSA_SELF_SIGNED)), 272 | ?assertEqual(ok, pkix:del_file(?EC_SELF_SIGNED)), 273 | commit_empty(). 274 | 275 | commit_valid_with_bad_cafile_test() -> 276 | File = path("valid-cert.pem"), 277 | CAFile = path("bad-pem.pem"), 278 | ?assertEqual(ok, pkix:add_file(File)), 279 | ?assertMatch({ok, [{_, {invalid_cert, _, unknown_ca}}], 280 | [], {CAFile, {bad_cert, _, bad_pem}}}, 281 | pkix:commit(test_dir(), [{cafile, CAFile}, {validate, hard}])), 282 | ?assertEqual(error, pkix:get_certfile()). 283 | 284 | commit_bad_dir_test() -> 285 | Dir = filename:join([test_dir(), "empty.pem", "foo"]), 286 | ?assertMatch({error, _, _}, pkix:commit(Dir)). 287 | 288 | no_domain_test() -> 289 | File = path("no-domain.pem"), 290 | ?assertEqual(ok, pkix:add_file(File)), 291 | ?assertEqual({ok, [], [], undefined}, 292 | pkix:commit(test_dir(), [{validate, false}])), 293 | {undefined, RSA, undefined} = pkix:get_certfile(<<>>), 294 | ?assertEqual(true, filelib:is_regular(RSA)), 295 | ?assertEqual(ok, pkix:del_file(File)), 296 | commit_empty(). 297 | 298 | get_certfile_test() -> 299 | File = path("no-domain.pem"), 300 | ?assertEqual(error, pkix:get_certfile()), 301 | ?assertEqual(ok, pkix:add_file(File)), 302 | ?assertEqual({ok, [], [], undefined}, 303 | pkix:commit(test_dir(), [{validate, false}])), 304 | {undefined, RSA, undefined} = pkix:get_certfile(), 305 | ?assertEqual(true, filelib:is_regular(RSA)), 306 | ?assertEqual(ok, pkix:del_file(File)), 307 | commit_empty(), 308 | ?assertEqual(error, pkix:get_certfile()). 309 | 310 | sort_by_validity_test() -> 311 | Invalid = path("rsa-self-signed.pem"), 312 | Valid = path("valid-cert.pem"), 313 | CAFile = path("ca.pem"), 314 | ?assertEqual(ok, pkix:add_file(Valid)), 315 | ?assertEqual(ok, pkix:add_file(Invalid)), 316 | ?assertMatch({ok, _, _, undefined}, 317 | pkix:commit(test_dir(), [{cafile, CAFile}])), 318 | {undefined, File, undefined} = pkix:get_certfile(<<"localhost">>), 319 | pem_files_are_equal(Valid, File), 320 | ?assertEqual(ok, pkix:del_file(Valid)), 321 | ?assertEqual(ok, pkix:del_file(Invalid)), 322 | commit_empty(). 323 | 324 | sort_by_expiration_date_test() -> 325 | Old = path("old.pem"), 326 | New = path("new.pem"), 327 | ?assertEqual(ok, pkix:add_file(Old)), 328 | ?assertEqual(ok, pkix:add_file(New)), 329 | ?assertMatch({ok, _, _, undefined}, pkix:commit(test_dir())), 330 | {undefined, File, undefined} = pkix:get_certfile(<<>>), 331 | pem_files_are_equal(New, File), 332 | ?assertEqual(ok, pkix:del_file(Old)), 333 | ?assertEqual(ok, pkix:del_file(New)), 334 | commit_empty(). 335 | 336 | cert_info_test() -> 337 | File = path("valid-cert.pem"), 338 | {ok, Certs, _} = pkix:read_file(File), 339 | [Cert] = maps:keys(Certs), 340 | ?assertEqual(error, pkix:get_cert_info(Cert)), 341 | ?assertEqual(ok, pkix:add_file(File)), 342 | Expiry = pkix:get_expiration_date(Cert), 343 | Files = [{iolist_to_binary(File), 1}], 344 | Domains = pkix:extract_domains(Cert), 345 | ?assertMatch({ok, #{files := Files, 346 | expiry := Expiry, 347 | domains := Domains}}, 348 | pkix:get_cert_info(Cert)), 349 | ?assertEqual(ok, pkix:del_file(File)), 350 | commit_empty(). 351 | 352 | notify_before_test_() -> 353 | {timeout, 10, fun notify_before/0}. 354 | 355 | notify_before() -> 356 | Old = path("localhost-old.pem"), 357 | New = path("localhost-new.pem"), 358 | Expired = path("expired.pem"), 359 | ?assertEqual(ok, pkix:add_file(Old)), 360 | ?assertEqual(ok, pkix:add_file(New)), 361 | ?assertEqual(ok, pkix:add_file(Expired)), 362 | {ok, OldCerts, _} = pkix:read_file(Old), 363 | {ok, NewCerts, _} = pkix:read_file(New), 364 | {ok, ExpiredCerts, _} = pkix:read_file(Expired), 365 | [OldCert] = maps:keys(OldCerts), 366 | [NewCert] = maps:keys(NewCerts), 367 | [ExpiredCert] = maps:keys(ExpiredCerts), 368 | Self = self(), 369 | NotifyFun = fun(Event) -> Self ! Event end, 370 | NotifyBefore = lists:map( 371 | fun({Seconds, Cert}) -> 372 | ExpDate = calendar:datetime_to_gregorian_seconds( 373 | pkix:get_expiration_date(Cert)), 374 | CurrDate = calendar:datetime_to_gregorian_seconds( 375 | pkix:current_datetime()), 376 | max(0, ExpDate - CurrDate - Seconds) 377 | end, [{0, ExpiredCert}, {2, OldCert}, {4, NewCert}]), 378 | ?assertMatch({ok, _, _, _}, 379 | pkix:commit(test_dir(), 380 | [{validate, false}, 381 | {notify_fun, NotifyFun}, 382 | {notify_before, NotifyBefore}])), 383 | recv_expiration([ExpiredCert, OldCert, OldCert, NewCert]), 384 | ?assertEqual(ok, pkix:del_file(Old)), 385 | ?assertEqual(ok, pkix:del_file(New)), 386 | ?assertEqual(ok, pkix:del_file(Expired)), 387 | commit_empty(). 388 | 389 | recv_expiration([Cert|Certs]) -> 390 | receive 391 | Msg -> 392 | ?assertMatch({cert_expired, Cert, _}, Msg), 393 | {_, _, Info} = Msg, 394 | ?assertEqual({ok, Info}, pkix:get_cert_info(Cert)), 395 | recv_expiration(Certs) 396 | end; 397 | recv_expiration([]) -> 398 | ok. 399 | 400 | unexpected_call_test() -> 401 | ?assertExit({timeout, _}, gen_server:call(pkix, eunit_call, 10)). 402 | 403 | unexpected_cast_test() -> 404 | ?assertEqual(ok, gen_server:cast(pkix, eunit_cast)). 405 | 406 | unexpected_info_test() -> 407 | ?assertEqual(eunit_info, erlang:send(pkix, eunit_info)). 408 | 409 | format_error_test() -> 410 | Bad = [missing_priv_key, bad_der, bad_pem, empty, 411 | encrypted, unknown_key_algo, unknown_key_type, 412 | unexpected_eof, nested_pem], 413 | Invalid = [cert_expired, invalid_issuer, invalid_signature, 414 | name_not_permitted, missing_basic_constraint, 415 | invalid_key_usage, selfsigned_peer, unknown_ca, 416 | unused_priv_key], 417 | lists:foreach( 418 | fun(BadErr) -> 419 | ?assertNotMatch("unexpected " ++ _, 420 | pkix:format_error({bad_cert, 1, BadErr})) 421 | end, Bad), 422 | lists:foreach( 423 | fun(InvalidErr) -> 424 | Unexpected = "at line 1: " ++ atom_to_list(InvalidErr), 425 | ?assertNotEqual(Unexpected, 426 | pkix:format_error({invalid_cert, 1, InvalidErr})) 427 | end, Invalid), 428 | ?assertEqual("at line 1: unexpected", 429 | pkix:format_error({invalid_cert, 1, unexpected})), 430 | ?assertEqual("unexpected", pkix:format_error(unexpected)), 431 | ?assertEqual("unexpected error: 123", pkix:format_error(123)). 432 | 433 | removed_before_commit_test() -> 434 | {ok, CWD} = file:get_cwd(), 435 | Src = path("valid-cert.pem"), 436 | Dst = filename:join(CWD, "valid-cert.pem"), 437 | ?assertMatch({ok, _}, file:copy(Src, Dst)), 438 | ?assertEqual(ok, pkix:add_file(Dst)), 439 | ?assertEqual(ok, file:delete(Dst)), 440 | ?assertMatch({ok, [{_, enoent}], [], undefined}, 441 | pkix:commit(test_dir())). 442 | 443 | gnutls_test() -> 444 | Cert = path("gnutls-cert.pem"), 445 | Key = path("gnutls-key.pem"), 446 | SubCA = path("gnutls-ca1.pem"), 447 | ?assertEqual(ok, pkix:add_file(Cert)), 448 | ?assertEqual(ok, pkix:add_file(Key)), 449 | ?assertEqual(ok, pkix:add_file(SubCA)), 450 | ?assertEqual({ok, [], [], undefined}, 451 | pkix:commit(test_dir(), [{cafile, path("gnutls-ca2.pem")}])), 452 | ?assertEqual(ok, pkix:del_file(Cert)), 453 | ?assertEqual(ok, pkix:del_file(Key)), 454 | ?assertEqual(ok, pkix:del_file(SubCA)), 455 | commit_empty(). 456 | 457 | stop_test() -> 458 | ?assertEqual(ok, pkix:stop()). 459 | 460 | %%%=================================================================== 461 | %%% Internal functions 462 | %%%=================================================================== 463 | test_dir() -> 464 | {ok, Cwd} = file:get_cwd(), 465 | CwdClean = case lists:reverse(filename:split(Cwd)) of 466 | [".eunit" | Tail] -> Tail; % when using rebar2 467 | Tail -> Tail % when using rebar3 468 | end, 469 | filename:join(lists:reverse(["test" | CwdClean])). 470 | 471 | path(File) -> 472 | unicode:characters_to_binary(filename:join(test_dir(), File)). 473 | 474 | commit_empty() -> 475 | ?assertEqual({ok, [], [], undefined}, pkix:commit(test_dir())), 476 | ?assertEqual([], pkix:get_certfiles()). 477 | 478 | pem_files_are_equal(File1, File2) -> 479 | {ok, Data1} = file:read_file(File1), 480 | {ok, Data2} = file:read_file(File2), 481 | PEM1 = lists:sort(public_key:pem_decode(Data1)), 482 | PEM2 = lists:sort(public_key:pem_decode(Data2)), 483 | ?assertEqual(hd(PEM1), hd(PEM2)). 484 | -------------------------------------------------------------------------------- /src/pkix.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% Created : 22 Sep 2018 by Evgeny Khramtsov 3 | %%% 4 | %%% Copyright (C) 2002-2024 ProcessOne, SARL. All Rights Reserved. 5 | %%% 6 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 7 | %%% you may not use this file except in compliance with the License. 8 | %%% You may obtain a copy of the License at 9 | %%% 10 | %%% http://www.apache.org/licenses/LICENSE-2.0 11 | %%% 12 | %%% Unless required by applicable law or agreed to in writing, software 13 | %%% distributed under the License is distributed on an "AS IS" BASIS, 14 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | %%% See the License for the specific language governing permissions and 16 | %%% limitations under the License. 17 | %%% 18 | %%%------------------------------------------------------------------- 19 | -module(pkix). 20 | -behaviour(gen_server). 21 | 22 | %% API 23 | -export([start/0, stop/0, start_link/0]). 24 | -export([add_file/1, del_file/1, read_file/1]). 25 | -export([commit/1, commit/2]). 26 | -export([get_certfile/0, get_certfile/1, get_certfiles/0, get_cafile/0]). 27 | -export([format_error/1, is_pem_file/1]). 28 | -export([get_cert_info/1]). 29 | %% gen_server callbacks 30 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 31 | terminate/2, code_change/3]). 32 | %% For tests only 33 | -export([get_expiration_date/1, current_datetime/0, extract_domains/1]). 34 | 35 | -include_lib("public_key/include/public_key.hrl"). 36 | -include_lib("kernel/include/file.hrl"). 37 | -define(CALL_TIMEOUT, timer:minutes(10)). 38 | -define(CERTFILE_TAB, pkix_certfiles). 39 | 40 | -record(pem, {file :: filename(), 41 | line :: line_num(), 42 | der :: binary()}). 43 | 44 | -record(state, {files = #{} :: files_map(), 45 | certs = #{} :: certs_map(), 46 | keys = #{} :: keys_map(), 47 | validate = false :: false | soft | hard, 48 | dir :: undefined | dirname(), 49 | cafile :: undefined | filename(), 50 | timers = sets:new() :: sets:set(), 51 | notify_fun :: undefined | notify_fun()}). 52 | 53 | -type state() :: #state{}. 54 | -type seconds() :: non_neg_integer(). 55 | -type commit_option() :: {cafile, file:filename_all()} | 56 | {validate, false | soft | hard} | 57 | {notify_before, [seconds()]} | 58 | {notify_fun, notify_fun()}. 59 | -type filename() :: binary(). 60 | -type dirname() :: binary(). 61 | -type line_num() :: pos_integer(). 62 | -type cert() :: #'OTPCertificate'{}. 63 | -type priv_key() :: public_key:private_key(). 64 | -type cert_path() :: {path, [cert()]}. 65 | -type cert_chain() :: {[cert()], priv_key()}. 66 | -type files_map() :: #{filename() => {calendar:datetime(), [cert()], [priv_key()]}}. 67 | -type certs_map() :: #{cert() => [#pem{}]}. 68 | -type keys_map() :: #{priv_key() => [#pem{}]}. 69 | -type pub_key() :: #'RSAPublicKey'{} | {integer(), #'Dss-Parms'{}} | #'ECPoint'{}. 70 | -type notify_event() :: {cert_expired, cert(), cert_info()}. 71 | -type notify_fun() :: fun((notify_event()) -> any()). 72 | -type cert_info() :: #{files := [{filename(), line_num()}, ...], 73 | expiry := calendar:datetime(), 74 | domains := [binary()]}. 75 | -type bad_cert_reason() :: missing_priv_key | bad_der | bad_pem | empty | 76 | encrypted | unknown_key_algo | unknown_key_type | 77 | unexpected_eof | nested_pem. 78 | -type invalid_cert_reason() :: cert_expired | invalid_issuer | invalid_signature | 79 | name_not_permitted | missing_basic_constraint | 80 | invalid_key_usage | selfsigned_peer | unknown_ca | 81 | unused_priv_key. 82 | -type bad_cert_error() :: {bad_cert, pos_integer(), bad_cert_reason()}. 83 | -type invalid_cert_error() :: {invalid_cert, pos_integer(), invalid_cert_reason()}. 84 | -type io_error() :: file:posix(). 85 | -type error_reason() :: bad_cert_error() | invalid_cert_error() | io_error(). 86 | -export_type([error_reason/0, notify_event/0, cert_info/0]). 87 | 88 | %%%=================================================================== 89 | %%% API 90 | %%%=================================================================== 91 | -spec start() -> ok | {error, term()}. 92 | start() -> 93 | case application:ensure_all_started(?MODULE) of 94 | {ok, _} -> ok; 95 | {error, _} = Err -> Err 96 | end. 97 | 98 | -spec stop() -> ok | {error, term()}. 99 | stop() -> 100 | application:stop(?MODULE). 101 | 102 | -spec start_link() -> {ok, Pid :: pid()} | 103 | {error, Error :: {already_started, pid()}} | 104 | {error, Error :: term()} | 105 | ignore. 106 | start_link() -> 107 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 108 | 109 | -spec add_file(file:filename_all()) -> ok | {error, bad_cert_error() | io_error()}. 110 | add_file(Path) -> 111 | gen_server:call(?MODULE, {add_file, prep_path(Path)}, ?CALL_TIMEOUT). 112 | 113 | -spec del_file(file:filename_all()) -> ok. 114 | del_file(Path) -> 115 | gen_server:call(?MODULE, {del_file, prep_path(Path)}, ?CALL_TIMEOUT). 116 | 117 | -spec read_file(file:filename_all()) -> {ok, #{cert() => [line_num()]}, 118 | #{priv_key() => [line_num()]}} | 119 | {error, bad_cert_error() | io_error()}. 120 | read_file(Path) -> 121 | case pem_decode_file(prep_path(Path)) of 122 | {ok, CertMap, KeyMap} -> 123 | Filter = fun(_, PemFiles) -> 124 | [Line || #pem{line = Line} <- PemFiles] 125 | end, 126 | {ok, maps:map(Filter, CertMap), maps:map(Filter, KeyMap)}; 127 | {error, _} = Err -> 128 | Err 129 | end. 130 | 131 | -spec is_pem_file(file:filename_all()) -> true | {false, bad_cert_error() | io_error()}. 132 | is_pem_file(Path) -> 133 | case pem_decode_file(prep_path(Path)) of 134 | {ok, _, _} -> true; 135 | {error, Why} -> {false, Why} 136 | end. 137 | 138 | -spec commit(file:filename_all()) -> 139 | {ok, Errors :: [{filename(), bad_cert_error() | invalid_cert_error() | io_error()}], 140 | Warnings :: [{filename(), bad_cert_error() | invalid_cert_error()}], 141 | CAError :: {filename(), bad_cert_error() | io_error()} | undefined} | 142 | {error, filename() | dirname(), io_error()}. 143 | commit(Dir) -> 144 | commit(Dir, []). 145 | 146 | -spec commit(file:filename_all(), [commit_option()]) -> 147 | {ok, Errors :: [{filename(), bad_cert_error() | invalid_cert_error() | io_error()}], 148 | Warnings :: [{filename(), bad_cert_error() | invalid_cert_error()}], 149 | CAError :: {filename(), bad_cert_error() | io_error()} | undefined} | 150 | {error, filename() | dirname(), io_error()}. 151 | commit(Dir, Opts) -> 152 | Validate = proplists:get_value(validate, Opts, soft), 153 | CAFile = case proplists:get_value(cafile, Opts) of 154 | undefined -> get_cafile(); 155 | Path -> prep_path(Path) 156 | end, 157 | NotifyBefore = proplists:get_value(notify_before, Opts, []), 158 | NotifyFun = proplists:get_value(notify_fun, Opts), 159 | gen_server:call(?MODULE, 160 | {commit, prep_path(Dir), CAFile, Validate, NotifyFun, 161 | lists:usort(NotifyBefore)}, 162 | ?CALL_TIMEOUT). 163 | 164 | -spec get_certfile() -> {EC :: filename() | undefined, 165 | RSA :: filename() | undefined, 166 | DSA :: filename() | undefined} | error. 167 | get_certfile() -> 168 | case ets:first(?CERTFILE_TAB) of 169 | '$end_of_table' -> error; 170 | Domain -> 171 | try ets:lookup_element(?CERTFILE_TAB, Domain, 2) 172 | catch _:badarg -> get_certfile() 173 | end 174 | end. 175 | 176 | -spec get_certfile(binary()) -> {EC :: filename() | undefined, 177 | RSA :: filename() | undefined, 178 | DSA :: filename() | undefined} | error. 179 | get_certfile(Domain) -> 180 | try ets:lookup_element(?CERTFILE_TAB, Domain, 2) 181 | catch _:badarg -> 182 | case set_glob(Domain) of 183 | <<>> -> error; 184 | GlobDomain -> 185 | try ets:lookup_element(?CERTFILE_TAB, GlobDomain, 2) 186 | catch _:badarg -> error 187 | end 188 | end 189 | end. 190 | 191 | -spec get_certfiles() -> [{binary(), [{filename(), ec | rsa | dsa}]}]. 192 | get_certfiles() -> 193 | ets:tab2list(?CERTFILE_TAB). 194 | 195 | -spec get_cafile() -> filename(). 196 | get_cafile() -> 197 | get_cafile(possible_cafile_locations()). 198 | 199 | -spec get_cert_info(cert()) -> {ok, cert_info()} | error. 200 | get_cert_info(Cert) -> 201 | gen_server:call(?MODULE, {cert_info, Cert}, ?CALL_TIMEOUT). 202 | 203 | -spec format_error(bad_cert_error() | invalid_cert_error() | io_error()) -> string(). 204 | format_error({bad_cert, _Line, empty}) -> 205 | "no PEM encoded certificate or private key found"; 206 | format_error({bad_cert, Line, bad_pem}) -> 207 | at_line(Line, "failed to decode from PEM format"); 208 | format_error({bad_cert, Line, bad_der}) -> 209 | at_line(Line, "failed to decode from DER format"); 210 | format_error({bad_cert, Line, unexpected_eof}) -> 211 | at_line(Line, "unexpected end of file"); 212 | format_error({bad_cert, Line, nested_pem}) -> 213 | at_line(Line, "nested PEM entry"); 214 | format_error({bad_cert, Line, encrypted}) -> 215 | at_line(Line, "encrypted certificate"); 216 | format_error({bad_cert, Line, unknown_key_algo}) -> 217 | at_line(Line, "unknown private key algorithm"); 218 | format_error({bad_cert, Line, unknown_key_type}) -> 219 | at_line(Line, "private key is of unknown type"); 220 | format_error({bad_cert, Line, missing_priv_key}) -> 221 | at_line(Line, "no matching private key found for this certificate"); 222 | format_error({invalid_cert, Line, cert_expired}) -> 223 | at_line(Line, "certificate is no longer valid as its expiration date has passed"); 224 | format_error({invalid_cert, Line, invalid_issuer}) -> 225 | at_line(Line, "certificate issuer name does not match the name of the " 226 | "issuer certificate"); 227 | format_error({invalid_cert, Line, invalid_signature}) -> 228 | at_line(Line, "certificate was not signed by its issuer certificate"); 229 | format_error({invalid_cert, Line, name_not_permitted}) -> 230 | at_line(Line, "invalid Subject Alternative Name extension"); 231 | format_error({invalid_cert, Line, missing_basic_constraint}) -> 232 | at_line(Line, "certificate, required to have the basic constraints extension, " 233 | "does not have a basic constraints extension"); 234 | format_error({invalid_cert, Line, invalid_key_usage}) -> 235 | at_line(Line, "certificate key is used in an invalid way according " 236 | "to the key-usage extension"); 237 | format_error({invalid_cert, Line, selfsigned_peer}) -> 238 | at_line(Line, "self-signed certificate"); 239 | format_error({invalid_cert, Line, unknown_ca}) -> 240 | at_line(Line, "certificate is signed by unknown CA"); 241 | format_error({invalid_cert, Line, unused_priv_key}) -> 242 | at_line(Line, "unused private key"); 243 | format_error({invalid_cert, Line, Unknown}) -> 244 | at_line(Line, io_lib:format("~w", [Unknown])); 245 | format_error(Posix) when is_atom(Posix) -> 246 | case file:format_error(Posix) of 247 | "unknown POSIX error" -> % Erlang/OTP 25 and older 248 | atom_to_list(Posix); 249 | [$u, $n, $k, $n, $o, $w, $n | _] -> % Erlang/OTP 26 and newer 250 | atom_to_list(Posix); 251 | Reason -> 252 | Reason 253 | end; 254 | format_error(Reason) -> 255 | lists:flatten(io_lib:format("unexpected error: ~w", [Reason])). 256 | 257 | %%%=================================================================== 258 | %%% gen_server callbacks 259 | %%%=================================================================== 260 | -spec init([]) -> {ok, state()}. 261 | init([]) -> 262 | process_flag(trap_exit, true), 263 | ets:new(?CERTFILE_TAB, [named_table, public, {read_concurrency, true}]), 264 | {ok, #state{}}. 265 | 266 | -spec handle_call(_, _, state()) -> {reply, term(), state()} | {noreply, state()}. 267 | handle_call({add_file, Path}, _, State) -> 268 | case add_file(Path, State) of 269 | {ok, State1} -> {reply, ok, State1}; 270 | {error, _} = Err -> {reply, Err, State} 271 | end; 272 | handle_call({del_file, Path}, _, State) -> 273 | State1 = del_file(Path, State), 274 | {reply, ok, State1}; 275 | handle_call({commit, Dir, CAFile, Validate, NotifyFun, NotifyBefore}, _From, State) -> 276 | State1 = cancel_timers(State), 277 | {BadCerts, State2} = reload_files(State1), 278 | case commit(State2, Dir, CAFile, Validate) of 279 | {ok, Certs, Keys, CertErrors, CertWarns, CAError} -> 280 | State3 = State2#state{dir = Dir, 281 | cafile = CAFile, 282 | notify_fun = NotifyFun, 283 | validate = Validate}, 284 | State4 = filter_state(State3, Certs, Keys), 285 | State5 = set_timers(State4, NotifyBefore), 286 | {reply, {ok, BadCerts ++ CertErrors, CertWarns, CAError}, State5}; 287 | {error, _, _} = Err -> 288 | {reply, Err, State} 289 | end; 290 | handle_call({cert_info, Cert}, _From, State) -> 291 | case maps:find(Cert, State#state.certs) of 292 | {ok, Files} -> 293 | {reply, {ok, cert_info(Cert, Files)}, State}; 294 | error -> 295 | {reply, error, State} 296 | end; 297 | handle_call(Request, _From, State) -> 298 | error_logger:warning_msg("Unexpected call: ~p", [Request]), 299 | {noreply, State}. 300 | 301 | -spec handle_cast(term(), state()) -> {noreply, state()}. 302 | handle_cast(Msg, State) -> 303 | error_logger:warning_msg("Unexpected cast: ~p", [Msg]), 304 | {noreply, State}. 305 | 306 | -spec handle_info(term(), state()) -> {noreply, state()}. 307 | handle_info({timeout, Timer, {cert_expired, Cert}}, State) -> 308 | case sets:is_element(Timer, State#state.timers) of 309 | true -> 310 | notify_expired(State, Cert), 311 | Timers1 = sets:del_element(Timer, State#state.timers), 312 | {noreply, State#state{timers = Timers1}}; 313 | false -> 314 | {noreply, State} 315 | end; 316 | handle_info(Info, State) -> 317 | error_logger:warning_msg("Unexpected info: ~p", [Info]), 318 | {noreply, State}. 319 | 320 | -spec terminate(normal | shutdown | {shutdown, term()} | term(), state()) -> any(). 321 | terminate(_Reason, State) -> 322 | case State#state.dir of 323 | undefined -> ok; 324 | Dir -> clear_dir(Dir, []) 325 | end. 326 | 327 | -spec code_change(term() | {down, term()}, state(), term()) -> {ok, state()}. 328 | code_change(_OldVsn, State, _Extra) -> 329 | {ok, State}. 330 | 331 | %%%=================================================================== 332 | %%% Certificate file loading/unloading 333 | %%%=================================================================== 334 | -spec add_file(filename(), state()) -> 335 | {ok, state()} | {error, bad_cert_error() | io_error()}. 336 | add_file(File, State) -> 337 | case mtime(File) of 338 | {ok, MTime} -> 339 | case maps:get(File, State#state.files, {0, [], []}) of 340 | {Time, _, _} when MTime =< Time -> 341 | {ok, State}; 342 | _ -> 343 | case pem_decode_file(File) of 344 | {ok, Certs, Keys} -> 345 | State1 = del_file(File, State), 346 | NewCerts = merge_maps(State1#state.certs, Certs), 347 | NewKeys = merge_maps(State1#state.keys, Keys), 348 | NewFiles = maps:put( 349 | File, 350 | {MTime, maps:keys(Certs), maps:keys(Keys)}, 351 | State1#state.files), 352 | {ok, State1#state{files = NewFiles, 353 | certs = NewCerts, 354 | keys = NewKeys}}; 355 | {error, _} = Err -> 356 | Err 357 | end 358 | end; 359 | {error, _} = Err -> 360 | Err 361 | end. 362 | 363 | -spec del_file(filename(), state()) -> state(). 364 | del_file(File, State) -> 365 | case maps:get(File, State#state.files, undefined) of 366 | undefined -> 367 | State; 368 | {_, Cs, Ks} -> 369 | Fold = fun(Obj, Acc) -> 370 | Pems = maps:get(Obj, Acc), 371 | Pems1 = [Pem || Pem <- Pems, Pem#pem.file /= File], 372 | case Pems1 of 373 | [] -> maps:remove(Obj, Acc); 374 | _ -> maps:put(Obj, Pems1, Acc) 375 | end 376 | end, 377 | NewFiles = maps:remove(File, State#state.files), 378 | NewCerts = lists:foldl(Fold, State#state.certs, Cs), 379 | NewKeys = lists:foldl(Fold, State#state.keys, Ks), 380 | State#state{files = NewFiles, certs = NewCerts, keys = NewKeys} 381 | end. 382 | 383 | -spec reload_files(state()) -> {[{filename(), bad_cert_error() | io_error()}], 384 | state()}. 385 | reload_files(State) -> 386 | Files = maps:keys(State#state.files), 387 | {Errs, State1} = lists:mapfoldl( 388 | fun(File, Acc) -> 389 | case add_file(File, Acc) of 390 | {ok, Acc1} -> 391 | {[], Acc1}; 392 | {error, Why} -> 393 | Acc1 = del_file(File, Acc), 394 | {[{File, Why}], Acc1} 395 | end 396 | end, State, Files), 397 | {lists:flatten(Errs), State1}. 398 | 399 | -spec filter_state(state(), [cert()], [priv_key()]) -> state(). 400 | filter_state(State, Certs, Keys) -> 401 | {Files1, NewCerts} = lists:foldl( 402 | fun(Cert, {Fs, Cs}) -> 403 | Pems = maps:get(Cert, State#state.certs), 404 | {lists:foldl( 405 | fun(Pem, Acc) -> 406 | sets:add_element( 407 | {Pem#pem.file, [Cert], []}, Acc) 408 | end, Fs, Pems), 409 | Cs#{Cert => Pems}} 410 | end, {sets:new(), #{}}, Certs), 411 | {Files2, NewKeys} = lists:foldl( 412 | fun(Key, {Fs, Ks}) -> 413 | Pems = maps:get(Key, State#state.keys), 414 | {lists:foldl( 415 | fun(Pem, Acc) -> 416 | sets:add_element( 417 | {Pem#pem.file, [], [Key]}, Acc) 418 | end, Fs, Pems), 419 | Ks#{Key => Pems}} 420 | end, {sets:new(), #{}}, Keys), 421 | NewFiles = lists:foldl( 422 | fun({File, Cert, Key}, Acc) -> 423 | case maps:get(File, Acc, undefined) of 424 | undefined -> 425 | {MTime, _, _} = maps:get(File, State#state.files), 426 | maps:put(File, {MTime, Cert, Key}, Acc); 427 | {MTime, Cs, Ks} -> 428 | maps:put(File, {MTime, Cert ++ Cs, Key ++ Ks}, Acc) 429 | end 430 | end, #{}, sets:to_list(sets:union(Files1, Files2))), 431 | State#state{files = NewFiles, certs = NewCerts, keys = NewKeys}. 432 | 433 | -spec set_timers(state(), [seconds()]) -> state(). 434 | set_timers(#state{notify_fun = NotifyFun} = State, 435 | NotifyBefore) when NotifyFun /= undefined -> 436 | {Timers, _} = 437 | lists:foldl( 438 | fun(_, {_, []} = Acc) -> Acc; 439 | (SecondsLeft, {Timers1, Certs1}) -> 440 | lists:foldl( 441 | fun({Cert, Files}, {Timers2, Certs2} = Acc) -> 442 | ExpireTime = calendar:datetime_to_gregorian_seconds( 443 | get_expiration_date(Cert)), 444 | CurrentTime = calendar:datetime_to_gregorian_seconds( 445 | current_datetime()), 446 | NotifyTime = ExpireTime - SecondsLeft, 447 | Timeout = NotifyTime - CurrentTime, 448 | if Timeout > 0 -> 449 | TRef = erlang:start_timer( 450 | timer:seconds(Timeout), 451 | self(), 452 | {cert_expired, Cert}), 453 | {sets:add_element(TRef, Timers2), [{Cert, Files}|Certs2]}; 454 | true -> 455 | notify_expired(NotifyFun, Cert, Files), 456 | Acc 457 | end 458 | end, {Timers1, []}, Certs1) 459 | end, {sets:new(), maps:to_list(State#state.certs)}, NotifyBefore), 460 | State#state{timers = Timers}; 461 | set_timers(State, _) -> 462 | State. 463 | 464 | -spec cancel_timers(state()) -> state(). 465 | cancel_timers(State) -> 466 | lists:foreach( 467 | fun(Timer) -> 468 | erlang:cancel_timer(Timer), 469 | receive {timeout, Timer, _} -> ok 470 | after 0 -> ok 471 | end 472 | end, sets:to_list(State#state.timers)), 473 | State#state{timers = sets:new()}. 474 | 475 | -spec notify_expired(state(), cert()) -> any(). 476 | notify_expired(#state{notify_fun = undefined}, _) -> 477 | ok; 478 | notify_expired(State, Cert) -> 479 | case maps:find(Cert, State#state.certs) of 480 | {ok, Files} -> 481 | notify_expired(State#state.notify_fun, Cert, Files); 482 | error -> 483 | ok 484 | end. 485 | 486 | -spec notify_expired(notify_fun(), cert(), [#pem{}]) -> any(). 487 | notify_expired(NotifyFun, Cert, Files) -> 488 | CertInfo = cert_info(Cert, Files), 489 | NotifyFun({cert_expired, Cert, CertInfo}). 490 | 491 | -spec cert_info(cert(), [#pem{}]) -> cert_info(). 492 | cert_info(Cert, Files) -> 493 | #{domains => extract_domains(Cert), 494 | files => [{File, Line} || #pem{file = File, line = Line} <- Files], 495 | expiry => get_expiration_date(Cert)}. 496 | 497 | %%%=================================================================== 498 | %%% Certificate file decoding 499 | %%%=================================================================== 500 | -spec pem_decode_file(filename()) -> {ok, certs_map(), keys_map()} | 501 | {error, bad_cert_error() | io_error()}. 502 | pem_decode_file(Path) -> 503 | case file:read_file(Path) of 504 | {ok, Data} -> 505 | Lines = re:split(Data, <<"\\R">>, [bsr_anycrlf]), 506 | case pem_decode(Lines, 1, []) of 507 | {ok, PEMs} -> 508 | pem_decode_entries(PEMs, Path, #{}, #{}); 509 | {error, _} = Err -> 510 | Err 511 | end; 512 | {error, _} = Err -> 513 | Err 514 | end. 515 | 516 | -spec pem_decode([binary()], pos_integer(), [{pos_integer(), binary()}]) -> 517 | {ok, [{pos_integer(), binary()}]} | 518 | {error, bad_cert_error()}. 519 | pem_decode(Lines, LineNum, PEMs) -> 520 | case pem_decode(Lines, LineNum, 0, []) of 521 | {ok, NewLines, NewLineNum, PEM} -> 522 | pem_decode(NewLines, NewLineNum, [PEM|PEMs]); 523 | eof -> 524 | {ok, lists:reverse(PEMs)}; 525 | {error, _} = Err -> 526 | Err 527 | end. 528 | 529 | -spec pem_decode([binary()], pos_integer(), non_neg_integer(), [binary()]) -> 530 | {ok, [binary()], pos_integer(), {pos_integer(), binary()}} | 531 | {error, bad_cert_error()} | eof. 532 | pem_decode([Line|Lines], LineNum, 0, []) -> 533 | case Line of 534 | <<"-----BEGIN ", _/binary>> -> 535 | pem_decode(Lines, LineNum+1, LineNum, [$\n, Line]); 536 | _ -> 537 | pem_decode(Lines, LineNum+1, 0, []) 538 | end; 539 | pem_decode([Line|Lines], LineNum, Begin, Buf) -> 540 | case Line of 541 | <<"-----END ", _/binary>> -> 542 | PEM = list_to_binary(lists:reverse([$\n, Line|Buf])), 543 | {ok, Lines, LineNum+1, {Begin, PEM}}; 544 | <<"-----BEGIN ", _/binary>> -> 545 | {error, {bad_cert, LineNum, nested_pem}}; 546 | _ -> 547 | pem_decode(Lines, LineNum+1, Begin, [$\n, Line|Buf]) 548 | end; 549 | pem_decode([], _, 0, []) -> 550 | eof; 551 | pem_decode([], _, Begin, _) -> 552 | {error, {bad_cert, Begin, unexpected_eof}}. 553 | 554 | -spec pem_decode_entries([{pos_integer(), binary()}], filename(), 555 | certs_map(), keys_map()) -> 556 | {ok, certs_map(), keys_map()} | {error, bad_cert_error()}. 557 | pem_decode_entries([{Begin, Data}|PEMs], File, Certs, PrivKeys) -> 558 | try public_key:pem_decode(Data) of 559 | [{_, DER, _} = PemEntry] -> 560 | P = #pem{file = File, der = DER, line = Begin}, 561 | try der_decode(PemEntry) of 562 | undefined -> 563 | pem_decode_entries(PEMs, File, Certs, PrivKeys); 564 | #'OTPCertificate'{} = Cert -> 565 | Certs1 = update_map(Cert, [P], Certs), 566 | pem_decode_entries(PEMs, File, Certs1, PrivKeys); 567 | PrivKey -> 568 | PrivKeys1 = update_map(PrivKey, [P], PrivKeys), 569 | pem_decode_entries(PEMs, File, Certs, PrivKeys1) 570 | catch _:{bad_cert, Why} -> 571 | {error, {bad_cert, Begin, Why}}; 572 | _:_ -> 573 | {error, {bad_cert, Begin, bad_der}} 574 | end; 575 | [] -> 576 | pem_decode_entries(PEMs, File, Certs, PrivKeys) 577 | catch _:_ -> 578 | {error, {bad_cert, Begin, bad_pem}} 579 | end; 580 | pem_decode_entries([], _File, Certs, PrivKeys) -> 581 | case maps:size(Certs) + maps:size(PrivKeys) of 582 | 0 -> {error, {bad_cert, 1, empty}}; 583 | _ -> {ok, Certs, PrivKeys} 584 | end. 585 | 586 | -spec der_decode(public_key:pem_entry()) -> cert() | priv_key() | undefined. 587 | der_decode({_, _, Flag}) when Flag /= not_encrypted -> 588 | erlang:error({bad_cert, encrypted}); 589 | der_decode({'Certificate', Der, _}) -> 590 | public_key:pkix_decode_cert(Der, otp); 591 | der_decode({'PrivateKeyInfo', Der, _}) -> 592 | case public_key:der_decode('PrivateKeyInfo', Der) of 593 | #'PrivateKeyInfo'{privateKeyAlgorithm = 594 | #'PrivateKeyInfo_privateKeyAlgorithm'{ 595 | algorithm = Algo}, 596 | privateKey = Key} -> 597 | KeyBin = iolist_to_binary(Key), 598 | case Algo of 599 | ?'rsaEncryption' -> 600 | public_key:der_decode('RSAPrivateKey', KeyBin); 601 | ?'id-dsa' -> 602 | public_key:der_decode('DSAPrivateKey', KeyBin); 603 | ?'id-ecPublicKey' -> 604 | public_key:der_decode('ECPrivateKey', KeyBin); 605 | _ -> 606 | erlang:error({bad_cert, unknown_key_algo}) 607 | end; 608 | #'RSAPrivateKey'{} = Key -> Key; 609 | #'DSAPrivateKey'{} = Key -> Key; 610 | #'ECPrivateKey'{} = Key -> Key; 611 | _ -> erlang:error({bad_cert, unknown_key_type}) 612 | end; 613 | der_decode({Tag, Der, _}) when Tag == 'RSAPrivateKey'; 614 | Tag == 'DSAPrivateKey'; 615 | Tag == 'ECPrivateKey' -> 616 | public_key:der_decode(Tag, Der); 617 | der_decode({_, _, _}) -> 618 | undefined. 619 | 620 | %%%=================================================================== 621 | %%% Certificate chains processing 622 | %%%=================================================================== 623 | -spec commit(state(), dirname(), filename(), false | soft | hard) -> 624 | {ok, [cert()], [priv_key()], 625 | [{filename(), bad_cert_error() | invalid_cert_error()}], 626 | [{filename(), invalid_cert_error()}], 627 | {filename(), bad_cert_error() | io_error()} | undefined} | 628 | {error, filename() | dirname(), io_error()}. 629 | commit(State, Dir, CAFile, ValidateHow) -> 630 | {Chains, BadCertsWithReason, UnusedKeysWithReason} = build_chains(State), 631 | {CAError, InvalidCertsWithReason} = validate(State, Chains, CAFile, ValidateHow), 632 | InvalidCerts = [C || {C, _} <- InvalidCertsWithReason], 633 | SortedChains = case ValidateHow of 634 | hard when CAError == undefined -> 635 | ValidChains = drop_invalid_chains(Chains, InvalidCerts), 636 | sort_chains(ValidChains, []); 637 | hard -> []; 638 | _ -> sort_chains(Chains, InvalidCerts) 639 | end, 640 | case store_chains(SortedChains, Dir, State) of 641 | {ok, StoredCerts, StoredKeys} -> 642 | Bad = map_errors(State#state.certs, bad_cert, BadCertsWithReason), 643 | Invalid = map_errors(State#state.certs, invalid_cert, InvalidCertsWithReason), 644 | Unused = map_errors(State#state.keys, invalid_cert, UnusedKeysWithReason), 645 | case ValidateHow of 646 | hard -> 647 | {ok, StoredCerts, StoredKeys, Bad ++ Invalid, Unused, CAError}; 648 | _ -> 649 | {ok, StoredCerts, StoredKeys, Bad, Invalid ++ Unused, CAError} 650 | end; 651 | {error, _, _} = Err -> 652 | Err 653 | end. 654 | 655 | -spec build_chains(state()) -> {[cert_chain()], 656 | [{cert(), bad_cert_reason()}], 657 | [{priv_key(), invalid_cert_reason()}]}. 658 | build_chains(State) -> 659 | CertPaths = get_cert_paths(maps:keys(State#state.certs)), 660 | Keys = maps:keys(State#state.keys), 661 | {Chains, BadCerts} = match_cert_keys(CertPaths, Keys), 662 | UnusedKeys = lists:foldl( 663 | fun({_Chain, Key}, Acc) -> 664 | maps:remove(Key, Acc) 665 | end, State#state.keys, Chains), 666 | UnusedKeysWithReason = maps:fold( 667 | fun(Key, _, Acc) -> 668 | [{Key, unused_priv_key}|Acc] 669 | end, [], UnusedKeys), 670 | {Chains, BadCerts, UnusedKeysWithReason}. 671 | 672 | -spec match_cert_keys([cert_path()], [priv_key()]) -> 673 | {[cert_chain()], [{cert(), bad_cert_reason()}]}. 674 | match_cert_keys(CertPaths, PrivKeys) -> 675 | KeyPairs = [{pubkey_from_privkey(PrivKey), PrivKey} || PrivKey <- PrivKeys], 676 | match_cert_keys(CertPaths, KeyPairs, [], []). 677 | 678 | -spec match_cert_keys([cert_path()], [{pub_key(), priv_key()}], 679 | [cert_chain()], [{cert(), bad_cert_reason()}]) -> 680 | {[cert_chain()], [{cert(), bad_cert_reason()}]}. 681 | match_cert_keys([{path, Certs}|CertPaths], KeyPairs, Chains, BadCerts) -> 682 | [Cert|_] = RevCerts = lists:reverse(Certs), 683 | PubKey = pubkey_from_cert(Cert), 684 | case lists:keyfind(PubKey, 1, KeyPairs) of 685 | false -> 686 | match_cert_keys(CertPaths, KeyPairs, Chains, 687 | [{Cert, missing_priv_key}|BadCerts]); 688 | {_, PrivKey} -> 689 | match_cert_keys(CertPaths, KeyPairs, 690 | [{RevCerts, PrivKey}|Chains], BadCerts) 691 | end; 692 | match_cert_keys([], _, Chains, BadCerts) -> 693 | {Chains, BadCerts}. 694 | 695 | -spec pubkey_from_cert(cert()) -> pub_key(). 696 | pubkey_from_cert(Cert) -> 697 | TBSCert = Cert#'OTPCertificate'.tbsCertificate, 698 | PubKeyInfo = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, 699 | SubjPubKey = PubKeyInfo#'OTPSubjectPublicKeyInfo'.subjectPublicKey, 700 | case PubKeyInfo#'OTPSubjectPublicKeyInfo'.algorithm of 701 | #'PublicKeyAlgorithm'{ 702 | algorithm = ?rsaEncryption} -> 703 | SubjPubKey; 704 | #'PublicKeyAlgorithm'{ 705 | algorithm = ?'id-dsa', 706 | parameters = {params, DSSParams}} -> 707 | {SubjPubKey, DSSParams}; 708 | #'PublicKeyAlgorithm'{ 709 | algorithm = ?'id-ecPublicKey'} -> 710 | SubjPubKey 711 | end. 712 | 713 | -spec pubkey_from_privkey(priv_key()) -> pub_key(). 714 | pubkey_from_privkey(#'RSAPrivateKey'{modulus = Modulus, 715 | publicExponent = Exp}) -> 716 | #'RSAPublicKey'{modulus = Modulus, 717 | publicExponent = Exp}; 718 | pubkey_from_privkey(#'DSAPrivateKey'{p = P, q = Q, g = G, y = Y}) -> 719 | {Y, #'Dss-Parms'{p = P, q = Q, g = G}}; 720 | pubkey_from_privkey(#'ECPrivateKey'{publicKey = Key}) -> 721 | #'ECPoint'{point = Key}. 722 | 723 | -spec cert_type(priv_key()) -> ec | rsa | dsa. 724 | cert_type(#'ECPrivateKey'{}) -> ec; 725 | cert_type(#'RSAPrivateKey'{}) -> rsa; 726 | cert_type(#'DSAPrivateKey'{}) -> dsa. 727 | 728 | -spec drop_invalid_chains([cert_chain()], [cert()]) -> [cert_chain()]. 729 | drop_invalid_chains(Chains, InvalidCerts) -> 730 | lists:filter( 731 | fun({[Cert|_], _}) -> 732 | not lists:member(Cert, InvalidCerts) 733 | end, Chains). 734 | 735 | -spec sort_chains([cert_chain()], [cert()]) -> [cert_chain()]. 736 | sort_chains(Chains, InvalidCerts) -> 737 | lists:sort( 738 | fun({[Cert1|_], _}, {[Cert2|_], _}) -> 739 | IsValid1 = not lists:member(Cert1, InvalidCerts), 740 | IsValid2 = not lists:member(Cert2, InvalidCerts), 741 | if IsValid1 and not IsValid2 -> 742 | false; 743 | IsValid2 and not IsValid1 -> 744 | true; 745 | true -> 746 | compare_expiration_date(Cert1, Cert2) 747 | end 748 | end, Chains). 749 | 750 | -spec map_errors(certs_map() | keys_map(), bad_cert | invalid_cert, 751 | [{cert() | priv_key(), bad_cert_reason() | invalid_cert_reason()}]) -> 752 | [{filename(), bad_cert_error() | invalid_cert_error()}]. 753 | map_errors(Map, Type, CertsWithReason) -> 754 | lists:flatmap( 755 | fun({Cert, Reason}) -> 756 | lists:map( 757 | fun(#pem{file = File, line = Line}) -> 758 | {File, {Type, Line, Reason}} 759 | end, maps:get(Cert, Map)) 760 | end, CertsWithReason). 761 | 762 | %%%=================================================================== 763 | %%% Certificates storage 764 | %%%=================================================================== 765 | -spec store_chains([cert_chain()], dirname(), state()) -> 766 | {ok, [cert()], [priv_key()]} | 767 | {error, filename() | dirname(), io_error()}. 768 | store_chains(Chains, Dir, State) -> 769 | case State#state.dir of 770 | Dir -> 771 | store_chains(Chains, Dir, State, #{}, #{}, #{}); 772 | _ -> 773 | case filelib:ensure_dir(filename:join(Dir, "foo")) of 774 | ok -> 775 | clear_dir(Dir, []), 776 | store_chains(Chains, Dir, State, #{}, #{}, #{}); 777 | {error, Why} -> 778 | {error, Dir, Why} 779 | end 780 | end. 781 | 782 | -spec store_chains([cert_chain()], dirname(), state(), 783 | files_map(), certs_map(), keys_map()) -> 784 | {ok, [cert()], [priv_key()]} | 785 | {error, filename(), io_error()}. 786 | store_chains([{[Cert|_], PrivKey} = Chain|Chains], Dir, State, Files, Certs, Keys) -> 787 | case store_chain(Chain, Dir, State) of 788 | {ok, File} -> 789 | Type = cert_type(PrivKey), 790 | File1 = unicode:characters_to_binary(File), 791 | Domains = case extract_domains(Cert) of 792 | [] -> [<<>>]; 793 | Ds -> Ds 794 | end, 795 | {Files1, Certs1, Keys1} = 796 | lists:foldl( 797 | fun(Domain, {Fs, Cs, Ks}) -> 798 | FList = maps:get(Domain, Fs, []), 799 | {Fs#{Domain => [{Type, File1}|FList]}, 800 | Cs#{Domain => element(1, Chain)}, 801 | Ks#{Domain => PrivKey}} 802 | end, {Files, Certs, Keys}, Domains), 803 | store_chains(Chains, Dir, State, Files1, Certs1, Keys1); 804 | {error, _, _} = Err -> 805 | Err 806 | end; 807 | store_chains([], Dir, _State, FilesMap, CertsMap, KeysMap) -> 808 | Old = ets:tab2list(?CERTFILE_TAB), 809 | New = maps:fold( 810 | fun(Domain, Files, Acc) -> 811 | [{Domain, {proplists:get_value(ec, Files), 812 | proplists:get_value(rsa, Files), 813 | proplists:get_value(dsa, Files)}}|Acc] 814 | end, [], FilesMap), 815 | ets:insert(?CERTFILE_TAB, New), 816 | lists:foreach( 817 | fun(Elem) -> 818 | ets:delete_object(?CERTFILE_TAB, Elem) 819 | end, Old -- New), 820 | NewFiles = lists:flatmap( 821 | fun({_, T}) -> 822 | [F || F <- tuple_to_list(T), F /= undefined] 823 | end, New), 824 | clear_dir(Dir, NewFiles), 825 | Certs = lists:flatten(maps:values(CertsMap)), 826 | Keys = maps:values(KeysMap), 827 | {ok, Certs, Keys}. 828 | 829 | -spec store_chain(cert_chain(), dirname(), state()) -> 830 | {ok, filename()} | {error, filename(), io_error()}. 831 | store_chain(Chain, Dir, State) -> 832 | Data = pem_encode(Chain, State), 833 | FileName = filename:join(Dir, sha1(Data)), 834 | case file:write_file(FileName, Data) of 835 | ok -> 836 | case file:change_mode(FileName, 8#600) of 837 | ok -> ok; 838 | {error, Why} -> 839 | error_logger:warning_msg( 840 | "Failed to change permissions of ~ts: ~s", 841 | [FileName, file:format_error(Why)]) 842 | end, 843 | {ok, FileName}; 844 | {error, Why} -> 845 | {error, FileName, Why} 846 | end. 847 | 848 | -spec pem_encode(cert_chain(), state()) -> binary(). 849 | pem_encode({Certs, Key}, State) -> 850 | PEM1 = lists:map( 851 | fun(Cert) -> 852 | DER = get_der(Cert, State#state.certs), 853 | PemEntry = {'Certificate', DER, not_encrypted}, 854 | Source = lists:map( 855 | fun(#pem{file = File, line = Line}) -> 856 | io_lib:format("From ~ts:~B~n", [File, Line]) 857 | end, maps:get(Cert, State#state.certs)), 858 | [Source, public_key:pem_encode([PemEntry])] 859 | end, Certs), 860 | PEM2 = [[io_lib:format("From ~ts:~B~n", [File, Line]) 861 | || #pem{file = File, line = Line} <- maps:get(Key, State#state.keys)], 862 | public_key:pem_encode( 863 | [{element(1, Key), get_der(Key, State#state.keys), not_encrypted}])], 864 | iolist_to_binary([PEM1, PEM2]). 865 | 866 | -spec get_der(cert() | priv_key(), certs_map() | keys_map()) -> binary(). 867 | get_der(Key, Map) -> 868 | [#pem{der = DER}|_] = maps:get(Key, Map), 869 | DER. 870 | 871 | %%%=================================================================== 872 | %%% Domains extraction 873 | %%%=================================================================== 874 | -spec extract_domains(cert()) -> [binary()]. 875 | extract_domains(Cert) -> 876 | TBSCert = Cert#'OTPCertificate'.tbsCertificate, 877 | {rdnSequence, Subject} = TBSCert#'OTPTBSCertificate'.subject, 878 | Extensions = TBSCert#'OTPTBSCertificate'.extensions, 879 | lists:usort( 880 | get_domain_from_subject(lists:flatten(Subject)) ++ 881 | get_domains_from_san(Extensions)). 882 | 883 | -spec get_domain_from_subject([#'AttributeTypeAndValue'{}]) -> [binary()]. 884 | get_domain_from_subject(AttrVals) -> 885 | case lists:keyfind(?'id-at-commonName', 886 | #'AttributeTypeAndValue'.type, 887 | AttrVals) of 888 | #'AttributeTypeAndValue'{value = {_, S}} -> 889 | [iolist_to_binary(S)]; 890 | _ -> 891 | [] 892 | end. 893 | 894 | -spec get_domains_from_san([#'Extension'{}] | asn1_NOVALUE) -> [binary()]. 895 | get_domains_from_san(Extensions) when is_list(Extensions) -> 896 | case lists:keyfind(?'id-ce-subjectAltName', 897 | #'Extension'.extnID, 898 | Extensions) of 899 | #'Extension'{extnValue = Vals} -> 900 | lists:flatmap( 901 | fun({dNSName, S}) -> 902 | [iolist_to_binary(S)]; 903 | (_) -> 904 | [] 905 | end, Vals); 906 | _ -> 907 | [] 908 | end; 909 | get_domains_from_san(_) -> 910 | []. 911 | 912 | %%%=================================================================== 913 | %%% Certificates graph 914 | %%%=================================================================== 915 | -spec get_cert_paths([cert()]) -> [cert_path()]. 916 | get_cert_paths(Certs) -> 917 | G = digraph:new([acyclic]), 918 | Paths = get_cert_paths(Certs, G), 919 | digraph:delete(G), 920 | Paths. 921 | 922 | -spec get_cert_paths([cert()], digraph:graph()) -> [cert_path()]. 923 | get_cert_paths(Certs, G) -> 924 | lists:foreach( 925 | fun(Cert) -> 926 | digraph:add_vertex(G, Cert) 927 | end, Certs), 928 | add_edges(G, Certs, Certs), 929 | lists:flatmap( 930 | fun(Cert) -> 931 | case digraph:in_degree(G, Cert) of 932 | 0 -> 933 | get_cert_path(G, [Cert]); 934 | _ -> 935 | [] 936 | end 937 | end, Certs). 938 | 939 | add_edges(G, [Cert1|T], L) -> 940 | case public_key:pkix_is_self_signed(Cert1) of 941 | true -> 942 | ok; 943 | false -> 944 | lists:foreach( 945 | fun(Cert2) when Cert1 /= Cert2 -> 946 | case public_key:pkix_is_issuer(Cert1, Cert2) of 947 | true -> 948 | digraph:add_edge(G, Cert1, Cert2); 949 | false -> 950 | ok 951 | end; 952 | (_) -> 953 | ok 954 | end, L) 955 | end, 956 | add_edges(G, T, L); 957 | add_edges(_, [], _) -> 958 | ok. 959 | 960 | get_cert_path(G, [Root|_] = Acc) -> 961 | case digraph:out_edges(G, Root) of 962 | [] -> 963 | [{path, Acc}]; 964 | Es -> 965 | lists:flatmap( 966 | fun(E) -> 967 | {_, _, V, _} = digraph:edge(G, E), 968 | get_cert_path(G, [V|Acc]) 969 | end, Es) 970 | end. 971 | 972 | %%%=================================================================== 973 | %%% Certificates chain validation 974 | %%%=================================================================== 975 | -spec validate(state(), [cert_chain()], filename(), false | soft | hard) -> 976 | {undefined | {filename(), bad_cert_error() | io_error()}, 977 | [{cert(), invalid_cert_reason()}]}. 978 | validate(_State, _Chains, _CAFile, false) -> 979 | {undefined, []}; 980 | validate(State, Chains, CAFile, _) -> 981 | {CAError, IssuerCerts} = case pem_decode_file(CAFile) of 982 | {error, Why} -> 983 | {{CAFile, Why}, []}; 984 | {ok, Ret, _} -> 985 | {undefined, maps:keys(Ret)} 986 | end, 987 | {CAError, 988 | lists:filtermap( 989 | fun({Certs, _PrivKey}) -> 990 | RevCerts = lists:reverse(Certs), 991 | case validate_path(State, RevCerts, IssuerCerts) of 992 | ok -> 993 | false; 994 | {error, Reason} -> 995 | {true, {hd(RevCerts), Reason}} 996 | end 997 | end, Chains)}. 998 | 999 | -spec validate_path(state(), [cert()], [cert()]) -> ok | {error, invalid_cert_reason()}. 1000 | validate_path(State, [Cert|_] = Certs, IssuerCerts) -> 1001 | case find_issuer_cert(Cert, IssuerCerts) of 1002 | {ok, IssuerCert} -> 1003 | DERs = [get_der(C, State#state.certs) || C <- Certs], 1004 | case public_key:pkix_path_validation(IssuerCert, DERs, []) of 1005 | {ok, _} -> 1006 | ok; 1007 | {error, {bad_cert, Reason}} -> 1008 | {error, Reason} 1009 | end; 1010 | error -> 1011 | case public_key:pkix_is_self_signed(Cert) of 1012 | true -> 1013 | {error, selfsigned_peer}; 1014 | false -> 1015 | {error, unknown_ca} 1016 | end 1017 | end. 1018 | 1019 | -spec find_issuer_cert(cert(), [cert()]) -> {ok, cert()} | error. 1020 | find_issuer_cert(Cert, [IssuerCert|IssuerCerts]) -> 1021 | case public_key:pkix_is_issuer(Cert, IssuerCert) of 1022 | true -> {ok, IssuerCert}; 1023 | false -> find_issuer_cert(Cert, IssuerCerts) 1024 | end; 1025 | find_issuer_cert(_Cert, []) -> 1026 | error. 1027 | 1028 | %%%=================================================================== 1029 | %%% Defaults 1030 | %%%=================================================================== 1031 | -spec get_cafile([filename()]) -> filename(). 1032 | get_cafile([File|Files]) -> 1033 | case filelib:is_regular(File) of 1034 | true -> File; 1035 | false -> get_cafile(Files) 1036 | end; 1037 | get_cafile([]) -> 1038 | Dir = case code:priv_dir(?MODULE) of 1039 | {error, _} -> "priv"; 1040 | Path -> Path 1041 | end, 1042 | prep_path(filename:join(Dir, "cacert.pem")). 1043 | 1044 | -spec possible_cafile_locations() -> [filename()]. 1045 | possible_cafile_locations() -> 1046 | %% TODO: add OSX/Darwin CA bundle path 1047 | [<<"/etc/ssl/certs/ca-certificates.crt">>, %% Debian/Ubuntu/Gentoo etc. 1048 | <<"/etc/pki/tls/certs/ca-bundle.crt">>, %% Fedora/RHEL 6 1049 | <<"/etc/ssl/ca-bundle.pem">>, %% OpenSUSE 1050 | <<"/etc/pki/tls/cacert.pem">>, %% OpenELEC 1051 | <<"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem">>, %% CentOS/RHEL 7 1052 | <<"/usr/local/etc/ssl/cert.pem">>, %% FreeBSD 1053 | <<"/etc/ssl/cert.pem">>, %% OpenBSD 1054 | <<"/usr/local/share/certs/ca-root-nss.crt">>, %% DragonFly 1055 | <<"/etc/openssl/certs/ca-certificates.crt">>, %% NetBSD 1056 | <<"/etc/certs/ca-certificates.crt">>, %% Solaris 11.2+ 1057 | <<"/etc/ssl/cacert.pem">>, %% OmniOS 1058 | <<"/sys/lib/tls/ca.pem">>]. %% Plan9 1059 | 1060 | %%%=================================================================== 1061 | %%% Auxiliary functions 1062 | %%%=================================================================== 1063 | -spec prep_path(file:filename_all()) -> filename(). 1064 | prep_path(Path0) -> 1065 | case filename:pathtype(Path0) of 1066 | relative -> 1067 | case file:get_cwd() of 1068 | {ok, CWD} -> 1069 | unicode:characters_to_binary(filename:join(CWD, Path0)); 1070 | {error, Reason} -> 1071 | error_logger:warning_msg( 1072 | "Failed to get current directory name: ~s", 1073 | [file:format_error(Reason)]), 1074 | unicode:characters_to_binary(Path0) 1075 | end; 1076 | _ -> 1077 | unicode:characters_to_binary(Path0) 1078 | end. 1079 | 1080 | -spec get_expiration_date(cert()) -> calendar:datetime(). 1081 | get_expiration_date(#'OTPCertificate'{ 1082 | tbsCertificate = 1083 | #'OTPTBSCertificate'{ 1084 | validity = #'Validity'{notAfter = NotAfter}}}) -> 1085 | get_datetime(NotAfter). 1086 | 1087 | -spec get_datetime({utcTime | generalTime, string()}) -> calendar:datetime(). 1088 | get_datetime({utcTime, [Y1,Y2|T]}) -> 1089 | get_datetime( 1090 | case list_to_integer([Y1,Y2]) of 1091 | N when N >= 50 -> {generalTime, [$1,$9,Y1,Y2|T]}; 1092 | _ -> {generalTime, [$2,$0,Y1,Y2|T]} 1093 | end); 1094 | get_datetime({generalTime, T1}) -> 1095 | [Y1,Y2,Y3,Y4,M1,M2,D1,D2,H1,H2,Mi1,Mi2,S1,S2,$*|_] = [C - $0 || C <- T1], 1096 | Date = {Y1*1000+Y2*100+Y3*10+Y4, M1*10+M2, D1*10+D2}, 1097 | Time = {H1*10+H2, Mi1*10+Mi2, S1*10+S2}, 1098 | {Date, Time}; 1099 | get_datetime(_) -> 1100 | {{0,0,0}, {0,0,0}}. 1101 | 1102 | -spec current_datetime() -> calendar:datetime(). 1103 | current_datetime() -> 1104 | calendar:now_to_datetime(erlang:timestamp()). 1105 | 1106 | %% Returns true if the first certificate has sooner expiration date 1107 | -spec compare_expiration_date(cert(), cert()) -> boolean(). 1108 | compare_expiration_date(Cert1, Cert2) -> 1109 | get_expiration_date(Cert1) =< get_expiration_date(Cert2). 1110 | 1111 | -spec sha1(iodata()) -> binary(). 1112 | sha1(Text) -> 1113 | Bin = crypto:hash(sha, Text), 1114 | to_hex(Bin). 1115 | 1116 | -spec to_hex(binary()) -> binary(). 1117 | to_hex(Bin) -> 1118 | << <<(digit_to_xchar(N div 16)), (digit_to_xchar(N rem 16))>> || <> <= Bin >>. 1119 | 1120 | -spec digit_to_xchar(char()) -> char(). 1121 | digit_to_xchar(D) when (D >= 0) and (D < 10) -> D + $0; 1122 | digit_to_xchar(D) -> D + $a - 10. 1123 | 1124 | -spec at_line(pos_integer(), iolist()) -> string(). 1125 | at_line(Line, List) -> 1126 | lists:flatten(io_lib:format("at line ~B: ~s", [Line, List])). 1127 | 1128 | -spec mtime(filename()) -> {ok, calendar:datetime() | undefined} | 1129 | {error, io_error()}. 1130 | mtime(File) -> 1131 | case file:read_file_info(File) of 1132 | {ok, #file_info{mtime = MTime}} -> {ok, MTime}; 1133 | {error, _} = Err -> Err 1134 | end. 1135 | 1136 | -spec clear_dir(dirname(), [filename()]) -> ok. 1137 | clear_dir(Dir, WhiteList) -> 1138 | Files = filelib:fold_files( 1139 | binary_to_list(Dir), "^[a-f0-9]{40}$", false, 1140 | fun(File, Acc) -> 1141 | [unicode:characters_to_binary(File)|Acc] 1142 | end, []), 1143 | lists:foreach(fun file:delete/1, Files -- WhiteList). 1144 | 1145 | -spec set_glob(binary()) -> binary(). 1146 | set_glob(<<$., Rest/binary>>) -> 1147 | <<$*, $., Rest/binary>>; 1148 | set_glob(<<_, Rest/binary>>) -> 1149 | set_glob(Rest); 1150 | set_glob(<<>>) -> 1151 | <<>>. 1152 | 1153 | -spec update_map(term(), list(), map()) -> map(). 1154 | update_map(Key, Val, Map) -> 1155 | Vals = maps:get(Key, Map, []), 1156 | maps:put(Key, Val ++ Vals, Map). 1157 | 1158 | -spec merge_maps(map(), map()) -> map(). 1159 | merge_maps(Map1, Map2) -> 1160 | maps:fold( 1161 | fun(Key, Val, Map) -> 1162 | update_map(Key, Val, Map) 1163 | end, Map1, Map2). 1164 | --------------------------------------------------------------------------------