pax_global_header 0000666 0000000 0000000 00000000064 14667627643 0014537 g ustar 00root root 0000000 0000000 52 comment=93d0de73ace05fa5d49e10a6577c6a5b01b4a3f6
enterprise-certificate-proxy-0.3.4/ 0000775 0000000 0000000 00000000000 14667627643 0017362 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/.github/ 0000775 0000000 0000000 00000000000 14667627643 0020722 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/.github/workflows/ 0000775 0000000 0000000 00000000000 14667627643 0022757 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/.github/workflows/test-client.yml 0000664 0000000 0000000 00000001070 14667627643 0025733 0 ustar 00root root 0000000 0000000 name: Build and Test Client
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.19
- name: Build
run: go build -v ./client/...
- name: Test
run: go test -v ./client/...
- name: Lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
working-directory: ./client
args: -E gofmt --max-same-issues 0
enterprise-certificate-proxy-0.3.4/.github/workflows/test-cshared.yml 0000664 0000000 0000000 00000000663 14667627643 0026075 0 ustar 00root root 0000000 0000000 name: Build and Test C-Shared Library
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.18
- name: Build
run: go build -buildmode=c-shared -v -o signer.so ./cshared/...
- name: Test
run: go test -v ./cshared/...
enterprise-certificate-proxy-0.3.4/.github/workflows/test-signer-darwin.yml 0000664 0000000 0000000 00000002530 14667627643 0027230 0 ustar 00root root 0000000 0000000 name: Build and Test Signer Darwin
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.19
- name: Build
working-directory: ./internal/signer/darwin
run: go build -v ./...
# The binary must be built before creating credentials so it can be added
# as a trusted application to the keychain.
- name: Build Test Binary
working-directory: ./internal/signer/darwin
run: go test -c ./keychain
- name: Create keychain credentials
run: ./scripts/keychain_cred.sh
- name: Run Test Binary
working-directory: ./internal/signer/darwin
run: ./keychain.test
- name: Lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
working-directory: ./internal/signer/darwin
args: -E gofmt --max-same-issues 0
- name: Create Binaries
run: ./build/scripts/darwin_amd64.sh && ./build/scripts/darwin_arm64.sh
- uses: actions/upload-artifact@v3
with:
name: darwin_amd64
path: ./build/bin/darwin_amd64/*
- uses: actions/upload-artifact@v3
with:
name: darwin_arm64
path: ./build/bin/darwin_arm64/*
enterprise-certificate-proxy-0.3.4/.github/workflows/test-signer-linux.yml 0000664 0000000 0000000 00000002071 14667627643 0027103 0 ustar 00root root 0000000 0000000 name: Build and Test Signer Linux
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Bootstrap SoftHSM Credentials
run: ./scripts/softhsm_setup.sh
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.19
- name: Build
working-directory: ./internal/signer/linux
run: go build -v ./...
- name: Test
working-directory: ./internal/signer/linux
run: go test -v ./... -testSlot=$(pkcs11-tool --list-slots --module "/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so" | grep -Eo "0x[A-Fa-f0-9]+" | head -n 1)
- name: Lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
working-directory: ./internal/signer/linux
args: -E gofmt --max-same-issues 0
- name: Create Binaries
run: ./build/scripts/linux_amd64.sh
- uses: actions/upload-artifact@v3
with:
name: linux_amd64
path: ./build/bin/linux_amd64/*
enterprise-certificate-proxy-0.3.4/.github/workflows/test-signer-windows.yml 0000664 0000000 0000000 00000001566 14667627643 0027446 0 ustar 00root root 0000000 0000000 name: Build and Test Signer Windows
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'
- name: Build
working-directory: ./internal/signer/windows
run: go build -v ./...
- name: Test
working-directory: ./internal/signer/windows
run: go test -v ./...
- name: Lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
working-directory: ./internal/signer/windows
args: -E gofmt --max-same-issues 0
- name: Create Binaries
run: .\build\scripts\windows_amd64.ps1
- uses: actions/upload-artifact@v3
with:
name: windows_amd64
path: .\build\bin\windows_amd64\*
enterprise-certificate-proxy-0.3.4/.gitignore 0000664 0000000 0000000 00000000064 14667627643 0021352 0 ustar 00root root 0000000 0000000 # MacOS
.DS_Store
# compiled binaries
build/bin/**
enterprise-certificate-proxy-0.3.4/CODEOWNERS 0000664 0000000 0000000 00000000132 14667627643 0020751 0 ustar 00root root 0000000 0000000 # Default owner for all directories not owned by others
* @andyrzhao @shinfan @clundin25
enterprise-certificate-proxy-0.3.4/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000010627 14667627643 0022167 0 ustar 00root root 0000000 0000000 # Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of
experience, education, socio-economic status, nationality, personal appearance,
race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, or to ban temporarily or permanently any
contributor for other behaviors that they deem inappropriate, threatening,
offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
This Code of Conduct also applies outside the project spaces when the Project
Steward has a reasonable belief that an individual's behavior may have a
negative impact on the project or its community.
## Conflict Resolution
We do not believe that all conflict is bad; healthy debate and disagreement
often yield positive results. However, it is never okay to be disrespectful or
to engage in behavior that violates the project’s code of conduct.
If you see someone violating the code of conduct, you are encouraged to address
the behavior directly with those involved. Many issues can be resolved quickly
and easily, and this gives people more control over the outcome of their
dispute. If you are unable to resolve the matter for any reason, or if the
behavior is threatening or harassing, report it. We are dedicated to providing
an environment where participants feel welcome and safe.
Reports should be directed to *[PROJECT STEWARD NAME(s) AND EMAIL(s)]*, the
Project Steward(s) for *[PROJECT NAME]*. It is the Project Steward’s duty to
receive and address reported violations of the code of conduct. They will then
work with a committee consisting of representatives from the Open Source
Programs Office and the Google Open Source Strategy team. If for any reason you
are uncomfortable reaching out to the Project Steward, please email
opensource@google.com.
We will investigate every complaint, but you may not receive a direct response.
We will use our discretion in determining when and how to follow up on reported
incidents, which may range from not taking action to permanent expulsion from
the project and project-sponsored spaces. We will notify the accused of the
report and provide them an opportunity to discuss it before any action is taken.
The identity of the reporter will be omitted from the details of the report
supplied to the accused. In potentially harmful situations, such as ongoing
harassment or threats to anyone's safety, we may take action without notice.
## Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
available at
https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
enterprise-certificate-proxy-0.3.4/CONTRIBUTING.md 0000664 0000000 0000000 00000002111 14667627643 0021606 0 ustar 00root root 0000000 0000000 # How to Contribute
We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.
## Contributor License Agreement
Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution;
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to to see
your current agreements on file or to sign a new one.
You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.
## Code Reviews
All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.
## Community Guidelines
This project follows [Google's Open Source Community
Guidelines](https://opensource.google/conduct/).
enterprise-certificate-proxy-0.3.4/LICENSE 0000664 0000000 0000000 00000026136 14667627643 0020377 0 ustar 00root root 0000000 0000000
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
enterprise-certificate-proxy-0.3.4/README.md 0000664 0000000 0000000 00000015177 14667627643 0020654 0 ustar 00root root 0000000 0000000 # Google Proxies for Enterprise Certificates (GA)
## Certificate-based-access
If you use [certificate-based access][cba] to protect your Google Cloud resources, the end user [device certificate][clientcert] is one of the credentials that is verified before access to a resource is granted. You can configure Google Cloud to use the device certificates in your operating system key store when verifying access to a resource from the gcloud CLI or Terraform by using the enterprise certificates feature.
## Google Enterprise Certificate Proxies (ECP)
Google Enterprise Certificate Proxies (ECP) are part of the [Google Cloud Zero Trust architecture][zerotrust] that enables mutual authentication with [client-side certificates][clientcert]. This repository contains a set of proxies/modules that can be used by clients or toolings to interact with certificates that are stored in [protected key storage systems][keystore].
To interact the client certificates, application code should not need to use most of these proxies within this repository directly. Instead, the application should leverage the clients and toolings provided by Google such as [Cloud SDK](https://cloud.google.com/sdk) to have a more convenient developer experience.
## Compatibility
The following platforms/keystores are supported by ECP:
- MacOS: __Keychain__
- Linux: __PKCS#11__
- Windows: __MY__
## User Guide
Before using ECP with your application/client, you should complete the policy configurations documented in [Enable CBA for Enterprise Certificate][enterprisecert]. The remainder of this README focuses on client configuration.
### Quick Start
1. Install gcloud CLI (Cloud SDK) at: https://cloud.google.com/sdk/docs/install. Install with the bundled python option enabled.
1. **Note:** gcloud version 416.0 or newer is required. Version 430.0 or newer is recommended.
1. For macOS and Linux, run the install.sh script after downloading it to complete installation.
```
$ ./google-cloud-sdk/install.sh
```
1. Install the ECP helper component:
```
$ gcloud components install enterprise-certificate-proxy
```
1. Initialize ECP certificate configuration:
* **MacOS** `$ gcloud auth enterprise-certificate-config create macos --issuer=`
* **Linux** `$ gcloud auth enterprise-certificate-config create linux --label= --module= --slot=`
* **Windows** `$ gcloud auth enterprise-certificate-config create windows --issuer= --provider= --store=`
1. Enable usage of client certificates through gcloud CLI config command:
```
$ gcloud config set context_aware/use_client_certificate true
```
1. You can now use gcloud to access CBA-protected GCP resources. For example:
```
$ gcloud pubsub topics list
```
### Manual Certificate Configuration
ECP relies on a certificate configuration JSON file to read all the metadata information for locating the certificate.
By default, it is named `certificate_config.json` and stored at the following location on the user's device:
* **Linux and MacOS**: `~/.config/gcloud/certificate_config.json`
* **Windows**: `%APPDATA%\gcloud\certificate_config.json`
You can put the JSON file in the location of your choice and set the path to it using:
```
$ gcloud config set context_aware/certificate_config_file_path ""
```
Another approach for setting the JSON file location is with the `GOOGLE_API_CERTIFICATE_CONFIG` environment variable.
```
$ export GOOGLE_API_CERTIFICATE_CONFIG=""
```
Below are examples of the certificate configuration file:
#### MacOS (Keychain)
```json
{
"cert_configs": {
"macos_keychain": {
"issuer": "YOUR_CERT_ISSUER"
}
},
"libs": {
"ecp": "[GCLOUD-INSTALL-LOCATION]/google-cloud-sdk/bin/ecp",
"ecp_client": "[GCLOUD-INSTALL-LOCATION]/google-cloud-sdk/platform/enterprise_cert/libecp.dylib",
"tls_offload": "[GCLOUD-INSTALL-LOCATION]/google-cloud-sdk/platform/enterprise_cert/libtls_offload.dylib"
},
"version": 1
}
```
#### Windows (MyStore)
```json
{
"cert_configs": {
"windows_store": {
"store": "MY",
"provider": "current_user",
"issuer": "YOUR_CERT_ISSUER"
}
},
"libs": {
"ecp": "[GCLOUD-INSTALL-LOCATION]/google-cloud-sdk/bin/ecp.exe",
"ecp_client": "[GCLOUD-INSTALL-LOCATION]/google-cloud-sdk/platform/enterprise_cert/libecp.dll",
"tls_offload": "[GCLOUD-INSTALL-LOCATION]/google-cloud-sdk/platform/enterprise_cert/libtls_offload.dll"
},
"version": 1
}
```
#### Linux (PKCS#11)
```json
{
"cert_configs": {
"pkcs11": {
"label": "YOUR_TOKEN_LABEL",
"user_pin": "YOUR_PIN",
"slot": "YOUR_SLOT",
"module": "The PKCS #11 module library file path"
}
},
"libs": {
"ecp": "[GCLOUD-INSTALL-LOCATION]/google-cloud-sdk/bin/ecp",
"ecp_client": "[GCLOUD-INSTALL-LOCATION]/google-cloud-sdk/platform/enterprise_cert/libecp.so",
"tls_offload": "[GCLOUD-INSTALL-LOCATION]/google-cloud-sdk/platform/enterprise_cert/libtls_offload.so"
},
"version": 1
}
```
### Logging
To enable logging set the `ENABLE_ENTERPRISE_CERTIFICATE_LOGS` environment variable.
#### Example
```
$ export ENABLE_ENTERPRISE_CERTIFICATE_LOGS=1 # Now the enterprise-certificate-proxy will output logs to stdout.
```
## Building ECP binaries from source
For amd64 MacOS, run `./build/scripts/darwin_amd64.sh`. The binaries will be placed in `build/bin/darwin_amd64` folder.
For amd64 Linux, run `./build/scripts/linux_amd64.sh`. The binaries will be placed in `build/bin/linux_amd64` folder.
For amd64 Windows, in powershell terminal, run `.\build\scripts\windows_amd64.ps1`. The binaries will be placed in `build\bin\windows_amd64` folder.
Note that gcc is required for compiling the Windows shared library. The easiest way to get gcc on Windows is to download Mingw64, and add "gcc.exe" to the powershell path.
## Contributing
Contributions to this library are always welcome and highly encouraged. See the [CONTRIBUTING](./CONTRIBUTING.md) documentation for more information on how to get started.
## License
Apache - See [LICENSE](./LICENSE) for more information.
[cba]: https://cloud.google.com/beyondcorp-enterprise/docs/securing-resources-with-certificate-based-access
[clientcert]: https://en.wikipedia.org/wiki/Client_certificate
[openssl]: https://wiki.openssl.org/index.php/Binaries
[keystore]: https://en.wikipedia.org/wiki/Key_management
[cloudsdk]: https://cloud.google.com/sdk
[enterprisecert]: https://cloud.google.com/beyondcorp-enterprise/docs/enable-cba-enterprise-certificates
[zerotrust]: https://cloud.google.com/blog/topics/developers-practitioners/zero-trust-and-beyondcorp-google-cloud
enterprise-certificate-proxy-0.3.4/SECURITY.md 0000664 0000000 0000000 00000000511 14667627643 0021150 0 ustar 00root root 0000000 0000000 # Security Policy
To report a security issue, please use [g.co/vulnz](https://g.co/vulnz).
The Google Security Team will respond within 5 working days of your report on g.co/vulnz.
We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue.
enterprise-certificate-proxy-0.3.4/build/ 0000775 0000000 0000000 00000000000 14667627643 0020461 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/build/scripts/ 0000775 0000000 0000000 00000000000 14667627643 0022150 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/build/scripts/darwin_amd64.sh 0000775 0000000 0000000 00000002054 14667627643 0024767 0 ustar 00root root 0000000 0000000 #!/bin/bash
# Copyright 2022 Google LLC.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -eux
CURRENT_TAG=$(cat version.txt)
# Create a folder to hold the binaries
rm -rf ./build/bin/darwin_amd64
mkdir -p ./build/bin/darwin_amd64
# Build the signer binary
cd ./internal/signer/darwin
go build
mv darwin ./../../../build/bin/darwin_amd64/ecp
cd ./../../..
# Build the signer library
go build -buildmode=c-shared -buildmode=c-shared -ldflags="-X=main.Version=$CURRENT_TAG" -o build/bin/darwin_amd64/libecp.dylib cshared/main.go
rm build/bin/darwin_amd64/libecp.h
enterprise-certificate-proxy-0.3.4/build/scripts/darwin_arm64.sh 0000775 0000000 0000000 00000002154 14667627643 0025006 0 ustar 00root root 0000000 0000000 #!/bin/bash
# Copyright 2022 Google LLC.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -eux
CURRENT_TAG=$(cat version.txt)
# Create a folder to hold the binaries
rm -rf ./build/bin/darwin_arm64
mkdir -p ./build/bin/darwin_arm64
# Build the signer binary
cd ./internal/signer/darwin
CGO_ENABLED=1 GO111MODULE=on GOARCH=arm64 go build
mv darwin ./../../../build/bin/darwin_arm64/ecp
cd ./../../..
# Build the signer library
CGO_ENABLED=1 GO111MODULE=on GOARCH=arm64 go build -buildmode=c-shared -ldflags="-X=main.Version=$CURRENT_TAG" -o build/bin/darwin_arm64/libecp.dylib cshared/main.go
rm build/bin/darwin_arm64/libecp.h
enterprise-certificate-proxy-0.3.4/build/scripts/linux_amd64.sh 0000775 0000000 0000000 00000002016 14667627643 0024640 0 ustar 00root root 0000000 0000000 #!/bin/bash
# Copyright 2022 Google LLC.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -eux
CURRENT_TAG=$(cat version.txt)
# Create a folder to hold the binaries
rm -rf ./build/bin/linux_amd64
mkdir -p ./build/bin/linux_amd64
# Build the signer library
go build -buildmode=c-shared -ldflags="-X=main.Version=$CURRENT_TAG" -o build/bin/linux_amd64/libecp.so cshared/main.go
rm build/bin/linux_amd64/libecp.h
# Build the signer binary
cd ./internal/signer/linux
go build
mv linux ./../../../build/bin/linux_amd64/ecp
cd ./../../..
enterprise-certificate-proxy-0.3.4/build/scripts/windows_amd64.ps1 0000664 0000000 0000000 00000002536 14667627643 0025270 0 ustar 00root root 0000000 0000000 # Copyright 2022 Google LLC.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
$OutputFolder = ".\build\bin\windows_amd64"
If (Test-Path $OutputFolder) {
# Remove existing binaries
Remove-Item -Path ".\build\bin\windows_amd64\*"
} else {
# Create the folder to hold the binaries
New-Item -Path $OutputFolder -ItemType Directory -Force
}
# Build the signer binary
Set-Location .\internal\signer\windows
go build
Move-Item .\windows.exe ..\..\..\build\bin\windows_amd64\ecp.exe
Set-Location ..\..\..\
# Build the signer library
# TODO: Add build version to shared DLL. https://github.com/googleapis/enterprise-certificate-proxy/issues/103
go build -buildmode=c-shared -o .\build\bin\windows_amd64\libecp.dll .\cshared\main.go
go build -buildmode=c-archive -o .\build\bin\windows_amd64\libecp.lib .\cshared\main.go
Remove-Item .\build\bin\windows_amd64\libecp.h
enterprise-certificate-proxy-0.3.4/client/ 0000775 0000000 0000000 00000000000 14667627643 0020640 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/client/client.go 0000664 0000000 0000000 00000016620 14667627643 0022452 0 ustar 00root root 0000000 0000000 // Copyright 2022 Google LLC.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package client is a cross-platform client for the signer binary (a.k.a."EnterpriseCertSigner").
//
// The signer binary is OS-specific, but exposes a standard set of APIs for the client to use.
package client
import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"encoding/gob"
"errors"
"fmt"
"io"
"net/rpc"
"os"
"os/exec"
"github.com/googleapis/enterprise-certificate-proxy/client/util"
)
const signAPI = "EnterpriseCertSigner.Sign"
const certificateChainAPI = "EnterpriseCertSigner.CertificateChain"
const publicKeyAPI = "EnterpriseCertSigner.Public"
const encryptAPI = "EnterpriseCertSigner.Encrypt"
const decryptAPI = "EnterpriseCertSigner.Decrypt"
// A Connection wraps a pair of unidirectional streams as an io.ReadWriteCloser.
type Connection struct {
io.ReadCloser
io.WriteCloser
}
// Close closes c's underlying ReadCloser and WriteCloser.
func (c *Connection) Close() error {
rerr := c.ReadCloser.Close()
werr := c.WriteCloser.Close()
if rerr != nil {
return rerr
}
return werr
}
func init() {
gob.Register(crypto.SHA256)
gob.Register(crypto.SHA384)
gob.Register(crypto.SHA512)
gob.Register(&rsa.PSSOptions{})
gob.Register(&rsa.OAEPOptions{})
}
// SignArgs contains arguments for a Sign API call.
type SignArgs struct {
Digest []byte // The content to sign.
Opts crypto.SignerOpts // Options for signing. Must implement HashFunc().
}
// EncryptArgs contains arguments for an Encrypt API call.
type EncryptArgs struct {
Plaintext []byte // The plaintext to encrypt.
Opts any // Options for encryption. Ex: an instance of crypto.Hash.
}
// DecryptArgs contains arguments to for a Decrypt API call.
type DecryptArgs struct {
Ciphertext []byte // The ciphertext to decrypt.
Opts crypto.DecrypterOpts // Options for decryption. Ex: an instance of *rsa.OAEPOptions.
}
// Key implements credential.Credential by holding the executed signer subprocess.
type Key struct {
cmd *exec.Cmd // Pointer to the signer subprocess.
client *rpc.Client // Pointer to the rpc client that communicates with the signer subprocess.
publicKey crypto.PublicKey // Public key of loaded certificate.
chain [][]byte // Certificate chain of loaded certificate.
}
// CertificateChain returns the credential as a raw X509 cert chain. This contains the public key.
func (k *Key) CertificateChain() [][]byte {
return k.chain
}
// Close closes the RPC connection and kills the signer subprocess.
// Call this to free up resources when the Key object is no longer needed.
func (k *Key) Close() error {
if err := k.cmd.Process.Kill(); err != nil {
return fmt.Errorf("failed to kill signer process: %w", err)
}
// Wait for cmd to exit and release resources. Since the process is forcefully killed, this
// will return a non-nil error (varies by OS), which we will ignore.
_ = k.cmd.Wait()
// The Pipes connecting the RPC client should have been closed when the signer subprocess was killed.
// Calling `k.client.Close()` before `k.cmd.Process.Kill()` or `k.cmd.Wait()` _will_ cause a segfault.
if err := k.client.Close(); err.Error() != "close |0: file already closed" {
return fmt.Errorf("failed to close RPC connection: %w", err)
}
return nil
}
// Public returns the public key for this Key.
func (k *Key) Public() crypto.PublicKey {
return k.publicKey
}
// Sign signs a message digest, using the specified signer opts. Implements crypto.Signer interface.
func (k *Key) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) (signed []byte, err error) {
if opts != nil && opts.HashFunc() != 0 && len(digest) != opts.HashFunc().Size() {
return nil, fmt.Errorf("Digest length of %v bytes does not match Hash function size of %v bytes", len(digest), opts.HashFunc().Size())
}
err = k.client.Call(signAPI, SignArgs{Digest: digest, Opts: opts}, &signed)
return
}
// Encrypt encrypts a plaintext msg into ciphertext, using the specified encrypt opts.
func (k *Key) Encrypt(_ io.Reader, msg []byte, opts any) (ciphertext []byte, err error) {
err = k.client.Call(encryptAPI, EncryptArgs{Plaintext: msg, Opts: opts}, &ciphertext)
return
}
// Decrypt decrypts a ciphertext msg into plaintext, using the specified decrypter opts. Implements crypto.Decrypter interface.
func (k *Key) Decrypt(_ io.Reader, msg []byte, opts crypto.DecrypterOpts) (plaintext []byte, err error) {
err = k.client.Call(decryptAPI, DecryptArgs{Ciphertext: msg, Opts: opts}, &plaintext)
return
}
// ErrCredUnavailable is a sentinel error that indicates ECP Cred is unavailable,
// possibly due to missing config or missing binary path.
var ErrCredUnavailable = errors.New("Cred is unavailable")
// Cred spawns a signer subprocess that listens on stdin/stdout to perform certificate
// related operations, including signing messages with the private key.
//
// The signer binary path is read from the specified configFilePath, if provided.
// Otherwise, use the default config file path.
//
// The config file also specifies which certificate the signer should use.
func Cred(configFilePath string) (*Key, error) {
if configFilePath == "" {
envFilePath := util.GetConfigFilePathFromEnv()
if envFilePath != "" {
configFilePath = envFilePath
} else {
configFilePath = util.GetDefaultConfigFilePath()
}
}
enterpriseCertSignerPath, err := util.LoadSignerBinaryPath(configFilePath)
if err != nil {
if errors.Is(err, util.ErrConfigUnavailable) {
return nil, ErrCredUnavailable
}
return nil, err
}
k := &Key{
cmd: exec.Command(enterpriseCertSignerPath, configFilePath),
}
// Redirect errors from subprocess to parent process.
k.cmd.Stderr = os.Stderr
// RPC client will communicate with subprocess over stdin/stdout.
kin, err := k.cmd.StdinPipe()
if err != nil {
return nil, err
}
kout, err := k.cmd.StdoutPipe()
if err != nil {
return nil, err
}
k.client = rpc.NewClient(&Connection{kout, kin})
if err := k.cmd.Start(); err != nil {
return nil, fmt.Errorf("starting enterprise cert signer subprocess: %w", err)
}
if err := k.client.Call(certificateChainAPI, struct{}{}, &k.chain); err != nil {
return nil, fmt.Errorf("failed to retrieve certificate chain: %w", err)
}
var publicKeyBytes []byte
if err := k.client.Call(publicKeyAPI, struct{}{}, &publicKeyBytes); err != nil {
return nil, fmt.Errorf("failed to retrieve public key: %w", err)
}
publicKey, err := x509.ParsePKIXPublicKey(publicKeyBytes)
if err != nil {
return nil, fmt.Errorf("failed to parse public key: %w", err)
}
var ok bool
k.publicKey, ok = publicKey.(crypto.PublicKey)
if !ok {
return nil, fmt.Errorf("invalid public key type: %T", publicKey)
}
switch pub := k.publicKey.(type) {
case *rsa.PublicKey:
if pub.Size() < 256 {
return nil, fmt.Errorf("RSA modulus size is less than 2048 bits: %v", pub.Size()*8)
}
case *ecdsa.PublicKey:
default:
return nil, fmt.Errorf("unsupported public key type: %v", pub)
}
return k, nil
}
enterprise-certificate-proxy-0.3.4/client/client_test.go 0000664 0000000 0000000 00000010550 14667627643 0023505 0 ustar 00root root 0000000 0000000 // Copyright 2022 Google LLC.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The tests in this file launches a mock signer binary "signer.go".
package client
import (
"bytes"
"crypto"
"crypto/rsa"
"encoding/json"
"errors"
"os"
"testing"
)
func TestClient_Cred_Success(t *testing.T) {
_, err := Cred("testdata/certificate_config.json")
if err != nil {
t.Errorf("Cred: got %v, want nil err", err)
}
}
func TestClient_Cred_ConfigMissing(t *testing.T) {
_, err := Cred("missing.json")
if got, want := err, ErrCredUnavailable; !errors.Is(got, want) {
t.Errorf("Cred: with missing config; got %v, want %v err", got, want)
}
}
func TestClient_Cred_BinaryPathMissing(t *testing.T) {
_, err := Cred("testdata/certificate_config_missing_path.json")
if got, want := err, ErrCredUnavailable; !errors.Is(got, want) {
t.Errorf("Cred: with missing ECP path; got %v, want %v err", got, want)
}
}
func TestClient_Cred_EnvOverride_ExplicitConfig(t *testing.T) {
configFilePath := "testdata/certificate_config.json"
os.Setenv("GOOGLE_API_CERTIFICATE_CONFIG", "testdata/certificate_config_missing_path.json")
_, err := Cred(configFilePath)
if err != nil {
t.Errorf("Cred: with explicit config and set env var; got %v, want %v err", err, nil)
}
}
func TestClient_Cred_EnvOverride_EmptyConfig(t *testing.T) {
configFilePath := ""
os.Setenv("GOOGLE_API_CERTIFICATE_CONFIG", "testdata/certificate_config_broken.json")
_, err := Cred(configFilePath)
var serr *json.SyntaxError
if got, want := err, &serr; !errors.As(got, want) {
t.Errorf("Cred: with empty config and set env var; got %v, want %v err", got, want)
}
}
func TestClient_Public(t *testing.T) {
key, err := Cred("testdata/certificate_config.json")
if err != nil {
t.Fatal(err)
}
if key.Public() == nil {
t.Error("Public: got nil, want non-nil Public Key")
}
}
func TestClient_CertificateChain(t *testing.T) {
key, err := Cred("testdata/certificate_config.json")
if err != nil {
t.Fatal(err)
}
if key.CertificateChain() == nil {
t.Error("CertificateChain: got nil, want non-nil Certificate Chain")
}
}
func TestClient_Sign(t *testing.T) {
key, err := Cred("testdata/certificate_config.json")
if err != nil {
t.Fatal(err)
}
signed, err := key.Sign(nil, []byte("testDigest"), nil)
if err != nil {
t.Fatal(err)
}
if got, want := signed, []byte("testDigest"); !bytes.Equal(got, want) {
t.Errorf("Sign: got %c, want %c", got, want)
}
}
func TestClientEncrypt(t *testing.T) {
key, err := Cred("testdata/certificate_config.json")
if err != nil {
t.Fatal(err)
}
plaintext := []byte("Plain text to encrypt")
_, err = key.Encrypt(nil, plaintext, crypto.SHA256)
if err != nil {
t.Errorf("Universal Client API encryption: got %v, want nil err", err)
return
}
}
func TestClientDecrypt(t *testing.T) {
key, err := Cred("testdata/certificate_config.json")
if err != nil {
t.Fatal(err)
}
byteSlice := []byte("Plain text to encrypt")
ciphertext, _ := key.Encrypt(nil, byteSlice, crypto.SHA256)
plaintext, err := key.Decrypt(nil, ciphertext, &rsa.OAEPOptions{Hash: crypto.SHA256})
if err != nil {
t.Errorf("Universal Client API decryption: got %v, want nil err", err)
return
}
if !bytes.Equal(byteSlice, plaintext) {
t.Errorf("Decryption message does not match original: got %v, want %v", plaintext, byteSlice)
}
}
func TestClient_Sign_HashSizeMismatch(t *testing.T) {
key, err := Cred("testdata/certificate_config.json")
if err != nil {
t.Fatal(err)
}
_, err = key.Sign(nil, []byte("testDigest"), crypto.SHA256)
if got, want := err.Error(), "Digest length of 10 bytes does not match Hash function size of 32 bytes"; got != want {
t.Errorf("Sign: got err %v, want err %v", got, want)
}
}
func TestClient_Close(t *testing.T) {
key, err := Cred("testdata/certificate_config.json")
if err != nil {
t.Fatal(err)
}
err = key.Close()
if err != nil {
t.Errorf("Close: got %v, want nil err", err)
}
}
enterprise-certificate-proxy-0.3.4/client/testdata/ 0000775 0000000 0000000 00000000000 14667627643 0022451 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/client/testdata/certificate_config.json 0000664 0000000 0000000 00000000201 14667627643 0027144 0 ustar 00root root 0000000 0000000 {
"cert_configs": {
"test": {
"issuer": "Test Issuer"
}
},
"libs": {
"ecp": "./testdata/signer.sh"
}
}
enterprise-certificate-proxy-0.3.4/client/testdata/certificate_config_broken.json 0000664 0000000 0000000 00000000030 14667627643 0030504 0 ustar 00root root 0000000 0000000 {
"cert_configs": {
}
enterprise-certificate-proxy-0.3.4/client/testdata/certificate_config_missing_path.json 0000664 0000000 0000000 00000000137 14667627643 0031721 0 ustar 00root root 0000000 0000000 {
"cert_configs": {
"test": {
"issuer": "Test Issuer"
}
},
"libs": {
}
}
enterprise-certificate-proxy-0.3.4/client/testdata/signer.sh 0000775 0000000 0000000 00000001214 14667627643 0024275 0 ustar 00root root 0000000 0000000 #!/bin/bash
# Copyright 2022 Google LLC.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
go run ../internal/signer/test/signer.go testdata/testcert.pem enterprise-certificate-proxy-0.3.4/client/testdata/testcert.pem 0000664 0000000 0000000 00000005601 14667627643 0025013 0 ustar 00root root 0000000 0000000 -----BEGIN CERTIFICATE-----
MIIDZjCCAk4CCQCN7UdavjYDjjANBgkqhkiG9w0BAQsFADB1MQswCQYDVQQGEwJV
UzELMAkGA1UECAwCV0ExDzANBgNVBAcMBlJlbnRvbjEMMAoGA1UECgwDQ0JBMQww
CgYDVQQLDANFQ1AxDTALBgNVBAMMBHRlc3QxHTAbBgkqhkiG9w0BCQEWDnRlc3RA
Z21haWwuY29tMB4XDTIyMDkxMTE2MzIwMVoXDTMyMDkwODE2MzIwMVowdTELMAkG
A1UEBhMCVVMxCzAJBgNVBAgMAldBMQ8wDQYDVQQHDAZSZW50b24xDDAKBgNVBAoM
A0NCQTEMMAoGA1UECwwDRUNQMQ0wCwYDVQQDDAR0ZXN0MR0wGwYJKoZIhvcNAQkB
Fg50ZXN0QGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
APlRXo2ji8rFYfF8ew7Fsi3KuHMvirW1/OGhhPaqGGDomvFpoAwf5MQn4RIOFzf0
KCy3bRSHjMJlRfINf/FgByjLik8NRcI3huHlDyAZS4Va4b/L4GIfA7jPuIu/HsAu
eGIOOncBpyKyRwaf2HhGAvy85MfWAvHr+3k0gL90nGQWFjvRDt+wyLLUZ5SIMDUT
x7aBji9qGAxX2sbiFB0C7chK4mwsPKowgK+fIgHkbqSIN6IyFIU6pLXGKJ1WrNBg
CHA2LPUE477GKinuaDq4PjQyVQF9MAQmK4hRu8N7COJeZunHWQJjACT5QRxmMiWp
H2dYbX6Wg3eXMRpbGVoLuHUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAFvfK3t5u
tK3+PPhkpCoEpcequn5vTOKDBSE95o3Od/RmNQEmUqSsuPtBd5ZVxKKa+ZapVowt
S9YFr5C9jgUleukLEYQNj0p8jrcZjVaUy5hmDynaIlkbtl5NHGyNOeJMJprA5ylV
wQ3ULnGjIxx3AsCEYeSp+eea6jztl5cvH6nGj6rI20lhrrHfKjxaGCRT+4X7NcXP
jSQrvaQjZKjs20iVX1f/En4OgR4FY5YJkMRhrebcoYnldkKzWjNpy3j/QwVWNzl3
1jfpeDUw8o7a4UDONMIwQjMQq05tqTh9WbL+6B2CEQnhPeKAGm8oHwyqdux8A9Nf
Lw4UcyjbQOSWlA==
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD5UV6No4vKxWHx
fHsOxbItyrhzL4q1tfzhoYT2qhhg6JrxaaAMH+TEJ+ESDhc39Cgst20Uh4zCZUXy
DX/xYAcoy4pPDUXCN4bh5Q8gGUuFWuG/y+BiHwO4z7iLvx7ALnhiDjp3AaciskcG
n9h4RgL8vOTH1gLx6/t5NIC/dJxkFhY70Q7fsMiy1GeUiDA1E8e2gY4vahgMV9rG
4hQdAu3ISuJsLDyqMICvnyIB5G6kiDeiMhSFOqS1xiidVqzQYAhwNiz1BOO+xiop
7mg6uD40MlUBfTAEJiuIUbvDewjiXmbpx1kCYwAk+UEcZjIlqR9nWG1+loN3lzEa
WxlaC7h1AgMBAAECggEBANhlYs9HO4d1CMzkQZ8RwtRyFuSLSDbtzZ89ZT3/Zwd9
/TY6eprrd9E11+mm50o+ljwxvPDLskXsRuiQBRPJSI2FFPgGSh0HuwAIo7c1nVIT
Dsw9NfWUe9OGH+TTruoZq41YUjCG871uxa0fQnEqO1+IyH4W6Bl4vJ14D6OdoDxR
JMzZyeddezQlyCS+Mi/jMi82YZWdCdhr1mpTtzVpWJEvKj0VUQFSd4ioS87/F7la
RT42Y1t6igfvHjnVV0w0mf+32UiLlDkOZ/xY215/9aYfJMhak7ctUGx3BgJgoyDP
hlRQxS2dzzgZGQrgjI+7jnyIbvGbhS2o6j8JorsTE6ECgYEA/aWK7iBtj1lH3O0t
6atOb/yT76k9bHaFgX0gU+H/8bxiGt0V2r4+IoAWtwJNgi3As9jP4yJPiCiRX2PW
yIhRZkEkoZ4uPSabPLtoKd/95sytiIQ57KRQGhUYehVz1Uockt4c6FfDi0XPkFek
/9N9Fv/sJxhWHp0hMn0u0oBVd2kCgYEA+6GLghm6roxQdq2kjAsBKHXmr1emfuzQ
BvucM3t8wh04I4r3jc2GpmCI428dtHQkYRTV5bdWrxI1MeIWxzumW1hXzjkuV+fI
WDX9gLCOB3d7mtHmwXunSHpwvZygXRZH3y4xYmTOpQgZAIxm1Gm6FsvMFVExF+UD
m06QWH0zgy0CgYBSp1s6db69864DRBauCnCo9XmPo2qsqYKfy5J5QzAQKf8eGeVB
PrUosOy1/j4bqaUd9gzoSwn3qKCWoQYgmqtL0vaI4+7VZns3syoiWyd1ykTSM6Rc
hL7FgRJU1iDE5D2jblWlMNQ70iftNWJDKzub/xGJO9j0aOekeD6FweQX4QKBgHwe
0FjpZhtJXTtdJchqeTTDC3o8SwVavLZlEESYyg5aKWHm33uUALI69erx2X40t+kn
ROceC2UqHxEvC7tU4hc2uYEg1YpI65sPbq8256gpONBCb4fK/dYTh18QTk38epFN
ENEPFptzJhoOJ37pdABgoJd3SDcYITJPi4YKpAk1AoGBAOaQN50lZQuIAab4hwmd
hEXpn2YA8+qU8K1Y4DdJNBdKt+JDzN30+B7qZ1vVvyDCCIEhoKAr9b/wjQHF2pC4
Vp89uLNKTLF6Pg4Wm+71MbDPFFRTyMghOPBn3vWQvj81sLMselg8eIjTO8XS7mQ7
hPJVfKseNBDOBE4OolLNAoBK
-----END PRIVATE KEY-----
enterprise-certificate-proxy-0.3.4/client/util/ 0000775 0000000 0000000 00000000000 14667627643 0021615 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/client/util/test_data/ 0000775 0000000 0000000 00000000000 14667627643 0023565 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/client/util/test_data/certificate_config.json 0000664 0000000 0000000 00000000141 14667627643 0030263 0 ustar 00root root 0000000 0000000 {
"libs": {
"ecp": "C:/Program Files (x86)/Google/Endpoint Verification/signer.exe"
}
}
enterprise-certificate-proxy-0.3.4/client/util/test_data/certificate_config_home_expansion.json 0000664 0000000 0000000 00000000067 14667627643 0033366 0 ustar 00root root 0000000 0000000 {
"libs": {
"ecp": "$HOME/ecp/signer"
}
} enterprise-certificate-proxy-0.3.4/client/util/test_data/certificate_config_tilde_expansion.json 0000664 0000000 0000000 00000000063 14667627643 0033533 0 ustar 00root root 0000000 0000000 {
"libs": {
"ecp": "~/ecp/signer"
}
} enterprise-certificate-proxy-0.3.4/client/util/util.go 0000664 0000000 0000000 00000005704 14667627643 0023127 0 ustar 00root root 0000000 0000000 // Copyright 2022 Google LLC.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package util provides helper functions for the client.
package util
import (
"encoding/json"
"errors"
"io"
"os"
"os/user"
"path/filepath"
"runtime"
"strings"
)
const configFileName = "certificate_config.json"
// EnterpriseCertificateConfig contains parameters for initializing signer.
type EnterpriseCertificateConfig struct {
Libs Libs `json:"libs"`
}
// Libs specifies the locations of helper libraries.
type Libs struct {
ECP string `json:"ecp"`
}
// ErrConfigUnavailable is a sentinel error that indicates ECP config is unavailable,
// possibly due to entire config missing or missing binary path.
var ErrConfigUnavailable = errors.New("Config is unavailable")
// LoadSignerBinaryPath retrieves the path of the signer binary from the config file.
func LoadSignerBinaryPath(configFilePath string) (path string, err error) {
jsonFile, err := os.Open(configFilePath)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return "", ErrConfigUnavailable
}
return "", err
}
byteValue, err := io.ReadAll(jsonFile)
if err != nil {
return "", err
}
var config EnterpriseCertificateConfig
err = json.Unmarshal(byteValue, &config)
if err != nil {
return "", err
}
signerBinaryPath := config.Libs.ECP
if signerBinaryPath == "" {
return "", ErrConfigUnavailable
}
signerBinaryPath = strings.ReplaceAll(signerBinaryPath, "~", guessHomeDir())
signerBinaryPath = strings.ReplaceAll(signerBinaryPath, "$HOME", guessHomeDir())
return signerBinaryPath, nil
}
func guessHomeDir() string {
// Prefer $HOME over user.Current due to glibc bug: golang.org/issue/13470
if v := os.Getenv("HOME"); v != "" {
return v
}
// Else, fall back to user.Current:
if u, err := user.Current(); err == nil {
return u.HomeDir
}
return ""
}
func getDefaultConfigFileDirectory() (directory string) {
if runtime.GOOS == "windows" {
return filepath.Join(os.Getenv("APPDATA"), "gcloud")
}
return filepath.Join(guessHomeDir(), ".config/gcloud")
}
// GetDefaultConfigFilePath returns the default path of the enterprise certificate config file created by gCloud.
func GetDefaultConfigFilePath() (path string) {
return filepath.Join(getDefaultConfigFileDirectory(), configFileName)
}
// GetConfigFilePathFromEnv returns the path associated with environment variable GOOGLE_API_CERTIFICATE_CONFIG
func GetConfigFilePathFromEnv() (path string) {
return os.Getenv("GOOGLE_API_CERTIFICATE_CONFIG")
}
enterprise-certificate-proxy-0.3.4/client/util/util_test.go 0000664 0000000 0000000 00000003516 14667627643 0024165 0 ustar 00root root 0000000 0000000 // Copyright 2022 Google LLC.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"os"
"testing"
)
func TestLoadSignerBinaryPath(t *testing.T) {
path, err := LoadSignerBinaryPath("./test_data/certificate_config.json")
if err != nil {
t.Errorf("LoadSignerBinaryPath error: %q", err)
}
want := "C:/Program Files (x86)/Google/Endpoint Verification/signer.exe"
if path != want {
t.Errorf("Expected path is %q, got: %q", want, path)
}
}
func TestLoadSignerBinaryPathHome(t *testing.T) {
path, err := LoadSignerBinaryPath("./test_data/certificate_config_home_expansion.json")
if err != nil {
t.Errorf("LoadSignerBinaryPath error: %q", err)
}
want := guessHomeDir() + "/ecp/signer"
if path != want {
t.Errorf("Expected path is %q, got: %q", want, path)
}
}
func TestLoadSignerBinaryPathTilde(t *testing.T) {
path, err := LoadSignerBinaryPath("./test_data/certificate_config_tilde_expansion.json")
if err != nil {
t.Errorf("LoadSignerBinaryPath error: %q", err)
}
want := guessHomeDir() + "/ecp/signer"
if path != want {
t.Errorf("Expected path is %q, got: %q", want, path)
}
}
func TestGetConfigFilePathFromEnv(t *testing.T) {
want := "/testpath"
os.Setenv("GOOGLE_API_CERTIFICATE_CONFIG", want)
path := GetConfigFilePathFromEnv()
if path != want {
t.Errorf("Expected path is %q, got: %q", want, path)
}
}
enterprise-certificate-proxy-0.3.4/cshared/ 0000775 0000000 0000000 00000000000 14667627643 0020773 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/cshared/main.go 0000664 0000000 0000000 00000014763 14667627643 0022261 0 ustar 00root root 0000000 0000000 // Copyright 2022 Google LLC.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This package is intended to be compiled into a C shared library for
// use by non-Golang clients to perform certificate and signing operations.
//
// The shared library exports language-specific wrappers around the Golang
// client APIs.
//
// Example compilation command:
// go build -buildmode=c-shared -o signer.dylib main.go
package main
/*
#include
*/
import "C"
import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"encoding/pem"
"io"
"log"
"os"
"unsafe"
"github.com/googleapis/enterprise-certificate-proxy/client"
)
// Version is generally set by the build command. Releases of ECP must have a specific version set.
// The version can be set when running `go build` like so `-ldflags="-X=main.Version=$CURRENT_TAG" `.
var Version = "dev"
// If ECP Logging is enabled return true
// Otherwise return false
func enableECPLogging() bool {
if os.Getenv("ENABLE_ENTERPRISE_CERTIFICATE_LOGS") != "" {
return true
}
log.SetOutput(io.Discard)
return false
}
func getCertPem(configFilePath string) []byte {
key, err := client.Cred(configFilePath)
if err != nil {
log.Printf("Could not create client using config %s: %v", configFilePath, err)
return nil
}
defer func() {
if err = key.Close(); err != nil {
log.Printf("Failed to clean up key. %v", err)
}
}()
certChain := key.CertificateChain()
certChainPem := []byte{}
for i := 0; i < len(certChain); i++ {
certPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certChain[i]})
certChainPem = append(certChainPem, certPem...)
}
return certChainPem
}
// Returns ECP's version number.
//
//export ECPVersion
func ECPVersion() *C.char {
return C.CString(Version)
}
// GetCertPem reads the contents of the certificate specified by configFilePath,
// storing the result inside a certHolder byte array of size certHolderLen.
//
// We must call it twice to get the cert. First time use nil for certHolder to get
// the cert length. Second time we pre-create an array of the cert length and
// call this function again to load the cert into the array.
//
//export GetCertPem
func GetCertPem(configFilePath *C.char, certHolder *byte, certHolderLen int) int {
enableECPLogging()
pemBytes := getCertPem(C.GoString(configFilePath))
if certHolder != nil {
cert := unsafe.Slice(certHolder, certHolderLen)
copy(cert, pemBytes)
}
return len(pemBytes)
}
// GetCertPemForPython reads the contents of the certificate specified by configFilePath,
// storing the result inside a certHolder byte array of size certHolderLen.
//
// We must call it twice to get the cert. First time use nil for certHolder to get
// the cert length. Second time we pre-create an array in Python of the cert length and
// call this function again to load the cert into the array.
//
// Deprecated: This API is deprecated in favor of GetCertPem and will be removed in future versions.
//
//export GetCertPemForPython
func GetCertPemForPython(configFilePath *C.char, certHolder *byte, certHolderLen int) int {
return GetCertPem(configFilePath, certHolder, certHolderLen)
}
// Sign signs a message digest of length digestLen using a certificate private key
// specified by configFilePath, storing the result inside a sigHolder byte array of size sigHolderLen.
//
//export Sign
func Sign(configFilePath *C.char, digest *byte, digestLen int, sigHolder *byte, sigHolderLen int) int {
// First create a handle around the specified certificate and private key.
enableECPLogging()
key, err := client.Cred(C.GoString(configFilePath))
if err != nil {
log.Printf("Could not create client using config %s: %v", C.GoString(configFilePath), err)
return 0
}
defer func() {
if err = key.Close(); err != nil {
log.Printf("Failed to clean up key. %v", err)
}
}()
var isRsa bool
switch key.Public().(type) {
case *ecdsa.PublicKey:
isRsa = false
log.Print("the key is ecdsa key")
case *rsa.PublicKey:
isRsa = true
log.Print("the key is rsa key")
default:
log.Printf("unsupported key type")
return 0
}
// Compute the signature
digestSlice := unsafe.Slice(digest, digestLen)
var signature []byte
var signErr error
if isRsa {
// For RSA key, we need to create the padding and flags for RSASSA-SHA256
opts := rsa.PSSOptions{
SaltLength: digestLen,
Hash: crypto.SHA256,
}
signature, signErr = key.Sign(nil, digestSlice, &opts)
} else {
signature, signErr = key.Sign(nil, digestSlice, crypto.SHA256)
}
if signErr != nil {
log.Printf("failed to sign hash: %v", signErr)
return 0
}
if sigHolderLen < len(signature) {
log.Printf("The sigHolder buffer size %d is smaller than the signature size %d", sigHolderLen, len(signature))
return 0
}
// Create a Go buffer around the output buffer and copy the signature into the buffer
outBytes := unsafe.Slice(sigHolder, sigHolderLen)
copy(outBytes, signature)
return len(signature)
}
// SignForPython signs a message digest of length digestLen using a certificate private key
// specified by configFilePath, storing the result inside a sigHolder byte array of size sigHolderLen.
//
// Deprecated: This API is deprecated in favor of Sign and will be removed in future versions.
//
//export SignForPython
func SignForPython(configFilePath *C.char, digest *byte, digestLen int, sigHolder *byte, sigHolderLen int) int {
return Sign(configFilePath, digest, digestLen, sigHolder, sigHolderLen);
}
// GetKeyType returns a string representing ECP's key type.
// The key is derived from the ECP configuration.
//
//export GetKeyType
func GetKeyType(configFilePath *C.char) *C.char {
key, err := client.Cred(C.GoString(configFilePath))
if err != nil {
log.Printf("Could not create client using config %s: %v", C.GoString(configFilePath), err)
return C.CString("unknown")
}
defer func() {
if err = key.Close(); err != nil {
log.Printf("Failed to clean up key. %v", err)
}
}()
switch key.Public().(type) {
case *ecdsa.PublicKey:
return C.CString("EC")
case *rsa.PublicKey:
return C.CString("RSA")
default:
return C.CString("unknown")
}
}
func main() {}
enterprise-certificate-proxy-0.3.4/darwin/ 0000775 0000000 0000000 00000000000 14667627643 0020646 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/darwin/client.go 0000664 0000000 0000000 00000005066 14667627643 0022462 0 ustar 00root root 0000000 0000000 // Copyright 2023 Google LLC.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build darwin && cgo
// +build darwin,cgo
// Package darwin contains a darwin-specific client for accessing the keychain APIs directly,
// bypassing the RPC mechanism of the universal client.
package darwin
import (
"crypto"
"io"
"github.com/googleapis/enterprise-certificate-proxy/internal/signer/darwin/keychain"
)
// SecureKey is a public wrapper for the internal keychain implementation.
type SecureKey struct {
key *keychain.Key
}
// CertificateChain returns the SecureKey's raw X509 cert chain. This contains the public key.
func (sk *SecureKey) CertificateChain() [][]byte {
return sk.key.CertificateChain()
}
// Public returns the public key for this SecureKey.
func (sk *SecureKey) Public() crypto.PublicKey {
return sk.key.Public()
}
// Sign signs a message digest, using the specified signer opts. Implements crypto.Signer interface.
func (sk *SecureKey) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) (signed []byte, err error) {
return sk.key.Sign(nil, digest, opts)
}
// Encrypt encrypts a plaintext msg into ciphertext, using the specified encrypt opts.
func (sk *SecureKey) Encrypt(_ io.Reader, msg []byte, opts any) (ciphertext []byte, err error) {
return sk.key.Encrypt(msg, opts)
}
// Decrypt decrypts a ciphertext msg into plaintext, using the specified decrypter opts. Implements crypto.Decrypter interface.
func (sk *SecureKey) Decrypt(_ io.Reader, msg []byte, opts crypto.DecrypterOpts) (plaintext []byte, err error) {
return sk.key.Decrypt(msg, opts)
}
// Close frees up resources associated with the underlying key.
func (sk *SecureKey) Close() {
sk.key.Close()
}
// NewSecureKey returns a handle to the first available certificate and private key pair in
// the MacOS Keychain matching the issuer CN filter. This includes both the current login keychain
// for the user as well as the system keychain.
func NewSecureKey(issuerCN string) (*SecureKey, error) {
k, err := keychain.Cred(issuerCN)
if err != nil {
return nil, err
}
return &SecureKey{key: k}, nil
}
enterprise-certificate-proxy-0.3.4/darwin/client_test.go 0000664 0000000 0000000 00000003160 14667627643 0023512 0 ustar 00root root 0000000 0000000 // Copyright 2023 Google LLC.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package darwin
import (
"bytes"
"crypto"
"crypto/rsa"
"testing"
)
const testIssuer = "TestIssuer"
func TestClientEncrypt(t *testing.T) {
secureKey, err := NewSecureKey(testIssuer)
if err != nil {
t.Errorf("Cred: got %v, want nil err", err)
return
}
plaintext := []byte("Plain text to encrypt")
_, err = secureKey.Encrypt(nil, plaintext, crypto.SHA256)
if err != nil {
t.Errorf("Client API encryption: got %v, want nil err", err)
return
}
}
func TestClientDecrypt(t *testing.T) {
secureKey, err := NewSecureKey(testIssuer)
if err != nil {
t.Errorf("Cred: got %v, want nil err", err)
return
}
byteSlice := []byte("Plain text to encrypt")
ciphertext, _ := secureKey.Encrypt(nil, byteSlice, crypto.SHA256)
plaintext, err := secureKey.Decrypt(nil, ciphertext, &rsa.OAEPOptions{Hash: crypto.SHA256})
if err != nil {
t.Errorf("Client API decryption: got %v, want nil err", err)
return
}
if !bytes.Equal(byteSlice, plaintext) {
t.Errorf("Decryption message does not match original: got %v, want %v", plaintext, byteSlice)
}
}
enterprise-certificate-proxy-0.3.4/go.mod 0000664 0000000 0000000 00000000253 14667627643 0020470 0 ustar 00root root 0000000 0000000 module github.com/googleapis/enterprise-certificate-proxy
go 1.19
require (
github.com/google/go-pkcs11 v0.3.0
golang.org/x/crypto v0.16.0
golang.org/x/sys v0.15.0
)
enterprise-certificate-proxy-0.3.4/go.sum 0000664 0000000 0000000 00000002474 14667627643 0020524 0 ustar 00root root 0000000 0000000 github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9 h1:OF1IPgv+F4NmqmJ98KTjdN97Vs1JxDPB3vbmYzV2dpk=
github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY=
github.com/google/go-pkcs11 v0.3.0 h1:PVRnTgtArZ3QQqTGtbtjtnIkzl2iY2kt24yqbrf7td8=
github.com/google/go-pkcs11 v0.3.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
enterprise-certificate-proxy-0.3.4/internal/ 0000775 0000000 0000000 00000000000 14667627643 0021176 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/internal/signer/ 0000775 0000000 0000000 00000000000 14667627643 0022465 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/internal/signer/darwin/ 0000775 0000000 0000000 00000000000 14667627643 0023751 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/internal/signer/darwin/keychain/ 0000775 0000000 0000000 00000000000 14667627643 0025544 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/internal/signer/darwin/keychain/keychain.go 0000664 0000000 0000000 00000047313 14667627643 0027676 0 ustar 00root root 0000000 0000000 // Copyright 2022 Google LLC.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build darwin && cgo
// +build darwin,cgo
// Package keychain contains functions for retrieving certificates from the Darwin Keychain.
package keychain
/*
#cgo CFLAGS: -mmacosx-version-min=10.12
#cgo LDFLAGS: -framework CoreFoundation -framework Security
#include
#include
*/
import "C"
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"runtime"
"sync"
"time"
"unsafe"
)
// Maps for translating from crypto.Hash to SecKeyAlgorithm.
// https://developer.apple.com/documentation/security/seckeyalgorithm
var (
ecdsaAlgorithms = map[crypto.Hash]C.CFStringRef{
crypto.SHA256: C.kSecKeyAlgorithmECDSASignatureDigestX962SHA256,
crypto.SHA384: C.kSecKeyAlgorithmECDSASignatureDigestX962SHA384,
crypto.SHA512: C.kSecKeyAlgorithmECDSASignatureDigestX962SHA512,
}
rsaRaw = map[crypto.Hash]C.CFStringRef{
crypto.SHA256: C.kSecKeyAlgorithmRSAEncryptionRaw,
}
rsaPKCS1v15Algorithms = map[crypto.Hash]C.CFStringRef{
crypto.SHA256: C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256,
crypto.SHA384: C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA384,
crypto.SHA512: C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA512,
}
rsaPSSAlgorithms = map[crypto.Hash]C.CFStringRef{
crypto.SHA256: C.kSecKeyAlgorithmRSASignatureDigestPSSSHA256,
crypto.SHA384: C.kSecKeyAlgorithmRSASignatureDigestPSSSHA384,
crypto.SHA512: C.kSecKeyAlgorithmRSASignatureDigestPSSSHA512,
}
rsaOAEPAlgorithms = map[crypto.Hash]C.CFStringRef{
crypto.SHA256: C.kSecKeyAlgorithmRSAEncryptionOAEPSHA256,
crypto.SHA384: C.kSecKeyAlgorithmRSAEncryptionOAEPSHA384,
crypto.SHA512: C.kSecKeyAlgorithmRSAEncryptionOAEPSHA512,
}
)
const unknownSecKeyAlgorithm = C.CFStringRef(0)
const invalidKey = C.SecKeyRef(0)
// cfStringToString returns a Go string given a CFString.
func cfStringToString(cfStr C.CFStringRef) string {
s := C.CFStringGetCStringPtr(cfStr, C.kCFStringEncodingUTF8)
if s != nil {
return C.GoString(s)
}
glyphLength := C.CFStringGetLength(cfStr) + 1
utf8Length := C.CFStringGetMaximumSizeForEncoding(glyphLength, C.kCFStringEncodingUTF8)
if s = (*C.char)(C.malloc(C.size_t(utf8Length))); s == nil {
panic("unable to allocate memory")
}
defer C.free(unsafe.Pointer(s))
if C.CFStringGetCString(cfStr, s, utf8Length, C.kCFStringEncodingUTF8) == 0 {
panic("unable to convert cfStringref to string")
}
return C.GoString(s)
}
func cfRelease(x unsafe.Pointer) {
C.CFRelease(C.CFTypeRef(x))
}
// cfError is an error type that owns a CFErrorRef, and obtains the error string
// by using CFErrorCopyDescription.
type cfError struct {
e C.CFErrorRef
}
// cfErrorFromRef converts a C.CFErrorRef to a cfError, taking ownership of the
// reference and releasing when the value is finalized.
func cfErrorFromRef(cfErr C.CFErrorRef) error {
if cfErr == 0 {
return nil
}
c := &cfError{e: cfErr}
runtime.SetFinalizer(c, func(x interface{}) {
C.CFRelease(C.CFTypeRef(x.(*cfError).e))
})
return c
}
func (e *cfError) Error() string {
s := C.CFErrorCopyDescription(C.CFErrorRef(e.e))
defer C.CFRelease(C.CFTypeRef(s))
return cfStringToString(s)
}
// keychainError is an error type that is based on an OSStatus return code, and
// obtains the error string with SecCopyErrorMessageString.
type keychainError C.OSStatus
func (e keychainError) Error() string {
s := C.SecCopyErrorMessageString(C.OSStatus(e), nil)
defer C.CFRelease(C.CFTypeRef(s))
return cfStringToString(s)
}
// cfDataToBytes turns a CFDataRef into a byte slice.
func cfDataToBytes(cfData C.CFDataRef) []byte {
return C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(cfData)), C.int(C.CFDataGetLength(cfData)))
}
// bytesToCFData turns a byte slice into a CFDataRef. Caller then "owns" the
// CFDataRef and must CFRelease the CFDataRef when done.
func bytesToCFData(buf []byte) C.CFDataRef {
return C.CFDataCreate(C.kCFAllocatorDefault, (*C.UInt8)(unsafe.Pointer(&buf[0])), C.CFIndex(len(buf)))
}
// int32ToCFNumber turns an int32 into a CFNumberRef. Caller then "owns"
// the CFNumberRef and must CFRelease the CFNumberRef when done.
func int32ToCFNumber(n int32) C.CFNumberRef {
return C.CFNumberCreate(C.kCFAllocatorDefault, C.kCFNumberSInt32Type, unsafe.Pointer(&n))
}
// Key is a wrapper around the Keychain reference that uses it to
// implement signing-related methods with Keychain functionality.
type Key struct {
privateKeyRef C.SecKeyRef
certs []*x509.Certificate
once sync.Once
publicKeyRef C.SecKeyRef
hash crypto.Hash
}
// newKey makes a new Key wrapper around the key reference,
// takes ownership of the reference, and sets up a finalizer to handle releasing
// the reference.
func newKey(privateKeyRef C.SecKeyRef, certs []*x509.Certificate, publicKeyRef C.SecKeyRef) (*Key, error) {
k := &Key{
privateKeyRef: privateKeyRef,
certs: certs,
publicKeyRef: publicKeyRef,
hash: crypto.SHA256,
}
// This struct now owns the key reference. Retain now and release on
// finalise in case the credential gets forgotten about.
C.CFRetain(C.CFTypeRef(privateKeyRef))
C.CFRetain(C.CFTypeRef(publicKeyRef))
runtime.SetFinalizer(k, func(x interface{}) {
x.(*Key).Close()
})
return k, nil
}
// CertificateChain returns the credential as a raw X509 cert chain. This
// contains the public key.
func (k *Key) CertificateChain() [][]byte {
rv := make([][]byte, len(k.certs))
for i, c := range k.certs {
rv[i] = c.Raw
}
return rv
}
// Close releases resources held by the credential.
func (k *Key) Close() error {
// Don't double-release references.
k.once.Do(func() {
C.CFRelease(C.CFTypeRef(k.privateKeyRef))
C.CFRelease(C.CFTypeRef(k.publicKeyRef))
})
return nil
}
// Public returns the corresponding public key for this Key. Good
// thing we extracted it when we created it.
func (k *Key) Public() crypto.PublicKey {
return k.certs[0].PublicKey
}
// Sign signs a message digest. Here, we pass off the signing to Keychain library.
func (k *Key) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
// Map the signing algorithm and hash function to a SecKeyAlgorithm constant.
var algorithms map[crypto.Hash]C.CFStringRef
switch pub := k.Public().(type) {
case *ecdsa.PublicKey:
algorithms = ecdsaAlgorithms
case *rsa.PublicKey:
if _, ok := opts.(*rsa.PSSOptions); ok {
algorithms = rsaPSSAlgorithms
break
}
algorithms = rsaPKCS1v15Algorithms
default:
return nil, fmt.Errorf("unsupported algorithm %T", pub)
}
algorithm, ok := algorithms[opts.HashFunc()]
if !ok {
return nil, fmt.Errorf("unsupported hash function %T", opts.HashFunc())
}
// Copy input over into CF-land.
cfDigest := bytesToCFData(digest)
defer C.CFRelease(C.CFTypeRef(cfDigest))
var cfErr C.CFErrorRef
sig := C.SecKeyCreateSignature(C.SecKeyRef(k.privateKeyRef), algorithm, C.CFDataRef(cfDigest), &cfErr)
if cfErr != 0 {
return nil, cfErrorFromRef(cfErr)
}
defer C.CFRelease(C.CFTypeRef(sig))
return cfDataToBytes(C.CFDataRef(sig)), nil
}
// Cred gets the first Credential (filtering on issuer) corresponding to
// available certificate and private key pairs (i.e. identities) available in
// the Keychain. This includes both the current login keychain for the user,
// and the system keychain.
func Cred(issuerCN string) (*Key, error) {
leafSearch := C.CFDictionaryCreateMutable(C.kCFAllocatorDefault, 5, &C.kCFTypeDictionaryKeyCallBacks, &C.kCFTypeDictionaryValueCallBacks)
defer C.CFRelease(C.CFTypeRef(unsafe.Pointer(leafSearch)))
// Get identities (certificate + private key pairs).
C.CFDictionaryAddValue(leafSearch, unsafe.Pointer(C.kSecClass), unsafe.Pointer(C.kSecClassIdentity))
// Get identities that are signing capable.
C.CFDictionaryAddValue(leafSearch, unsafe.Pointer(C.kSecAttrCanSign), unsafe.Pointer(C.kCFBooleanTrue))
// For each identity, give us the reference to it.
C.CFDictionaryAddValue(leafSearch, unsafe.Pointer(C.kSecReturnRef), unsafe.Pointer(C.kCFBooleanTrue))
// Be sure to list out all the matches.
C.CFDictionaryAddValue(leafSearch, unsafe.Pointer(C.kSecMatchLimit), unsafe.Pointer(C.kSecMatchLimitAll))
// Do the matching-item copy.
var leafMatches C.CFTypeRef
if errno := C.SecItemCopyMatching((C.CFDictionaryRef)(leafSearch), &leafMatches); errno != C.errSecSuccess {
return nil, keychainError(errno)
}
defer C.CFRelease(leafMatches)
signingIdents := C.CFArrayRef(leafMatches)
// Dump the certs into golang x509 Certificates.
var (
leafIdent C.SecIdentityRef
leaf *x509.Certificate
)
// Find the first valid leaf whose issuer (CA) matches the name in filter.
// Validation in identityToX509 covers Not Before, Not After and key alg.
for i := 0; i < int(C.CFArrayGetCount(signingIdents)) && leaf == nil; i++ {
identDict := C.CFArrayGetValueAtIndex(signingIdents, C.CFIndex(i))
xc, err := identityToX509(C.SecIdentityRef(identDict))
if err != nil {
continue
}
if xc.Issuer.CommonName == issuerCN {
leaf = xc
leafIdent = C.SecIdentityRef(identDict)
}
}
caSearch := C.CFDictionaryCreateMutable(C.kCFAllocatorDefault, 0, &C.kCFTypeDictionaryKeyCallBacks, &C.kCFTypeDictionaryValueCallBacks)
defer C.CFRelease(C.CFTypeRef(unsafe.Pointer(caSearch)))
// Get identities (certificates).
C.CFDictionaryAddValue(caSearch, unsafe.Pointer(C.kSecClass), unsafe.Pointer(C.kSecClassCertificate))
// For each identity, give us the reference to it.
C.CFDictionaryAddValue(caSearch, unsafe.Pointer(C.kSecReturnRef), unsafe.Pointer(C.kCFBooleanTrue))
// Be sure to list out all the matches.
C.CFDictionaryAddValue(caSearch, unsafe.Pointer(C.kSecMatchLimit), unsafe.Pointer(C.kSecMatchLimitAll))
// Do the matching-item copy.
var caMatches C.CFTypeRef
if errno := C.SecItemCopyMatching((C.CFDictionaryRef)(caSearch), &caMatches); errno != C.errSecSuccess {
return nil, keychainError(errno)
}
defer C.CFRelease(caMatches)
certRefs := C.CFArrayRef(caMatches)
// Validate and dump the certs into golang x509 Certificates.
var allCerts []*x509.Certificate
for i := 0; i < int(C.CFArrayGetCount(certRefs)); i++ {
refDict := C.CFArrayGetValueAtIndex(certRefs, C.CFIndex(i))
if xc, err := certRefToX509(C.SecCertificateRef(refDict)); err == nil {
allCerts = append(allCerts, xc)
}
}
// Build a certificate chain from leaf by matching prev.RawIssuer to
// next.RawSubject across all valid certificates in the keychain.
var (
certs []*x509.Certificate
prev, next *x509.Certificate
)
for prev = leaf; prev != nil; prev, next = next, nil {
certs = append(certs, prev)
for _, xc := range allCerts {
if certIn(xc, certs) {
continue // finite chains only, mmmmkay.
}
if bytes.Equal(prev.RawIssuer, xc.RawSubject) && prev.CheckSignatureFrom(xc) == nil {
// Prefer certificates with later expirations.
if next == nil || xc.NotAfter.After(next.NotAfter) {
next = xc
}
}
}
}
if len(certs) == 0 {
return nil, fmt.Errorf("no key found with issuer common name %q", issuerCN)
}
skr, err := identityToPrivateSecKeyRef(leafIdent)
if err != nil {
return nil, err
}
pubKey, err := identityToPublicSecKeyRef(leafIdent)
if err != nil {
return nil, err
}
defer C.CFRelease(C.CFTypeRef(skr))
return newKey(skr, certs, pubKey)
}
// identityToX509 converts a single CFDictionary that contains the item ref and
// attribute dictionary into an x509.Certificate.
func identityToX509(ident C.SecIdentityRef) (*x509.Certificate, error) {
var certRef C.SecCertificateRef
if errno := C.SecIdentityCopyCertificate(ident, &certRef); errno != 0 {
return nil, keychainError(errno)
}
defer C.CFRelease(C.CFTypeRef(certRef))
return certRefToX509(certRef)
}
// certRefToX509 converts a single C.SecCertificateRef into an *x509.Certificate.
func certRefToX509(certRef C.SecCertificateRef) (*x509.Certificate, error) {
// Export the PEM-encoded certificate to a CFDataRef.
var certPEMData C.CFDataRef
if errno := C.SecItemExport(C.CFTypeRef(certRef), C.kSecFormatUnknown, C.kSecItemPemArmour, nil, &certPEMData); errno != 0 {
return nil, keychainError(errno)
}
defer C.CFRelease(C.CFTypeRef(certPEMData))
certPEM := cfDataToBytes(certPEMData)
// This part based on crypto/tls.
var certDERBlock *pem.Block
for {
certDERBlock, certPEM = pem.Decode(certPEM)
if certDERBlock == nil {
return nil, fmt.Errorf("failed to parse certificate PEM data")
}
if certDERBlock.Type == "CERTIFICATE" {
// found it
break
}
}
// Check the certificate is OK by the x509 library, and obtain the
// public key algorithm (which I assume is the same as the private key
// algorithm). This also filters out certs missing critical extensions.
xc, err := x509.ParseCertificate(certDERBlock.Bytes)
if err != nil {
return nil, err
}
switch xc.PublicKey.(type) {
case *rsa.PublicKey, *ecdsa.PublicKey:
default:
return nil, fmt.Errorf("unsupported key type %T", xc.PublicKey)
}
// Check the certificate is valid
if n := time.Now(); n.Before(xc.NotBefore) || n.After(xc.NotAfter) {
return nil, fmt.Errorf("certificate not valid")
}
return xc, nil
}
// identityToSecKeyRef converts a single CFDictionary that contains the item ref and
// attribute dictionary into a SecKeyRef for its private key.
func identityToPrivateSecKeyRef(ident C.SecIdentityRef) (C.SecKeyRef, error) {
// Get the private key (ref). Note that "Copy" in "CopyPrivateKey"
// refers to "the create rule" of CoreFoundation memory management, and
// does not actually copy the private key---it gives us a copy of the
// reference that we now own.
var ref C.SecKeyRef
if errno := C.SecIdentityCopyPrivateKey(C.SecIdentityRef(ident), &ref); errno != 0 {
return 0, keychainError(errno)
}
return ref, nil
}
func identityToPublicSecKeyRef(ident C.SecIdentityRef) (C.SecKeyRef, error) {
var key C.SecKeyRef
var certRef C.SecCertificateRef
if errno := C.SecIdentityCopyCertificate(ident, &certRef); errno != 0 {
return 0, keychainError(errno)
}
defer C.CFRelease(C.CFTypeRef(certRef))
key = C.SecCertificateCopyKey(certRef)
if key == invalidKey {
return 0, fmt.Errorf("public key was NULL. Key might have an encoding issue or use an unsupported algorithm")
}
return key, nil
}
func stringIn(s string, ss []string) bool {
for _, s2 := range ss {
if s == s2 {
return true
}
}
return false
}
func certIn(xc *x509.Certificate, xcs []*x509.Certificate) bool {
for _, xc2 := range xcs {
if xc.Equal(xc2) {
return true
}
}
return false
}
func (k *Key) getPaddingSize() int {
algorithms, algoErr := k.getEncryptAlgorithm()
if algoErr != nil {
fmt.Printf("algorithm is unsupported. only RSA algorithms are supported. %v", algoErr)
}
// Each padding scheme has varying number of bytes.
pssPaddingBytes := 20
oaepPaddingBytes := 130
pkcsPaddingBytes := 11
switch algorithms {
case C.kSecKeyAlgorithmRSASignatureDigestPSSSHA256,
C.kSecKeyAlgorithmRSASignatureDigestPSSSHA384,
C.kSecKeyAlgorithmRSASignatureDigestPSSSHA512:
return pssPaddingBytes
case C.kSecKeyAlgorithmRSAEncryptionOAEPSHA256,
C.kSecKeyAlgorithmRSAEncryptionOAEPSHA384,
C.kSecKeyAlgorithmRSAEncryptionOAEPSHA512:
return oaepPaddingBytes
case C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256,
C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA384,
C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA512:
return pkcsPaddingBytes
default:
return int(unknownSecKeyAlgorithm)
}
}
func (k *Key) checkDataSize(plaintext []byte) error {
// Plaintext data must be smaller than the key's block size minus padding space.
sizeLim := uint64(C.SecKeyGetBlockSize(k.publicKeyRef)) - uint64(k.getPaddingSize())
if uint64(len(plaintext)) >= sizeLim {
return fmt.Errorf("plaintext is too long")
}
return nil
}
func (k *Key) getRSAEncryptAlgorithm() (C.SecKeyAlgorithm, error) {
var algorithms map[crypto.Hash]C.CFStringRef
switch pub := k.Public().(type) {
case *rsa.PublicKey:
if C.SecKeyIsAlgorithmSupported(k.publicKeyRef, C.kSecKeyOperationTypeEncrypt, C.kSecKeyAlgorithmRSASignatureDigestPSSSHA256) == 1 {
algorithms = rsaPSSAlgorithms
} else if C.SecKeyIsAlgorithmSupported(k.publicKeyRef, C.kSecKeyOperationTypeEncrypt, C.kSecKeyAlgorithmRSAEncryptionOAEPSHA256) == 1 {
algorithms = rsaOAEPAlgorithms
} else if C.SecKeyIsAlgorithmSupported(k.publicKeyRef, C.kSecKeyOperationTypeEncrypt, C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256) == 1 {
algorithms = rsaPKCS1v15Algorithms
} else {
return unknownSecKeyAlgorithm, fmt.Errorf("unknown RSA argument. Only supports PSS, OAEP, and PKCS1v1.5 %T", pub)
}
default:
return unknownSecKeyAlgorithm, fmt.Errorf("algorithm is unsupported. only RSA algorithms are supported. %T", pub)
}
return algorithms[k.hash], nil
}
func (k *Key) getEncryptAlgorithm() (C.SecKeyAlgorithm, error) {
if k.hash == 0 {
k.hash = crypto.SHA256
}
return k.getRSAEncryptAlgorithm()
}
func (k *Key) getRSADecryptAlgorithm() (C.SecKeyAlgorithm, error) {
var algorithms map[crypto.Hash]C.CFStringRef
switch pub := k.Public().(type) {
case *rsa.PublicKey:
if C.SecKeyIsAlgorithmSupported(k.publicKeyRef, C.kSecKeyOperationTypeDecrypt, C.kSecKeyAlgorithmRSASignatureDigestPSSSHA256) == 1 {
algorithms = rsaPSSAlgorithms
} else if C.SecKeyIsAlgorithmSupported(k.publicKeyRef, C.kSecKeyOperationTypeDecrypt, C.kSecKeyAlgorithmRSAEncryptionOAEPSHA256) == 1 {
algorithms = rsaOAEPAlgorithms
} else if C.SecKeyIsAlgorithmSupported(k.publicKeyRef, C.kSecKeyOperationTypeDecrypt, C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256) == 1 {
algorithms = rsaPKCS1v15Algorithms
} else {
return unknownSecKeyAlgorithm, fmt.Errorf("unknown RSA argument. Only supports PSS, OAEP, and PKCS1v1.5 %T", pub)
}
default:
return unknownSecKeyAlgorithm, fmt.Errorf("algorithm is unsupported. only RSA algorithms are supported. %T", pub)
}
return algorithms[k.hash], nil
}
func (k *Key) getDecryptAlgorithm() (C.SecKeyAlgorithm, error) {
return k.getRSADecryptAlgorithm()
}
// Encrypt encrypts a plaintext message digest using the public key. Here, we pass off the encryption to Keychain library.
func (k *Key) Encrypt(plaintext []byte, opts any) ([]byte, error) {
if hash, ok := opts.(crypto.Hash); ok {
k.hash = hash
} else {
return nil, fmt.Errorf("Unsupported encrypt opts: %v", opts)
}
pub := k.publicKeyRef
algorithm, err := k.getEncryptAlgorithm()
if err != nil {
return nil, err
}
if err := k.checkDataSize(plaintext); err != nil {
return nil, err
}
msg := bytesToCFData(plaintext)
var cfErr C.CFErrorRef
bytes := C.SecKeyCreateEncryptedData(pub, algorithm, msg, &cfErr)
if cfErr != 0 {
return nil, cfErrorFromRef(cfErr)
}
ciphertext := cfDataToBytes(bytes)
return ciphertext, cfErrorFromRef(cfErr)
}
// Decrypt decrypts a ciphertext message digest using the private key. Here, we pass off the decryption to Keychain library.
// Currently, only *rsa.OAEPOptions is supported for opts.
func (k *Key) Decrypt(ciphertext []byte, opts crypto.DecrypterOpts) ([]byte, error) {
if oaepOpts, ok := opts.(*rsa.OAEPOptions); ok {
k.hash = oaepOpts.Hash
} else {
return nil, fmt.Errorf("Unsupported DecrypterOpts: %v", opts)
}
priv := k.privateKeyRef
algorithm, err := k.getDecryptAlgorithm()
if err != nil {
return nil, err
}
msg := bytesToCFData(ciphertext)
var cfErr C.CFErrorRef
bytes := C.SecKeyCreateDecryptedData(priv, algorithm, msg, &cfErr)
if cfErr != 0 {
return nil, cfErrorFromRef(cfErr)
}
plaintext := cfDataToBytes(bytes)
return plaintext, cfErrorFromRef(cfErr)
}
enterprise-certificate-proxy-0.3.4/internal/signer/darwin/keychain/keychain_test.go 0000664 0000000 0000000 00000005774 14667627643 0030742 0 ustar 00root root 0000000 0000000 // Copyright 2022 Google LLC.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build darwin && cgo
// +build darwin,cgo
package keychain
import (
"bytes"
"crypto"
"crypto/rsa"
"testing"
"unsafe"
)
const testIssuer = "TestIssuer"
func TestKeychainError(t *testing.T) {
tests := []struct {
e keychainError
want string
}{
{e: keychainError(0), want: "No error."},
{e: keychainError(-4), want: "Function or operation not implemented."},
}
for i, test := range tests {
if got := test.e.Error(); got != test.want {
t.Errorf("test %d: %#v.Error() = %q, want %q", i, test.e, got, test.want)
}
}
}
func TestBytesToCFDataRoundTrip(t *testing.T) {
want := []byte("an arbitrary and yet coherent byte slice!")
d := bytesToCFData(want)
defer cfRelease(unsafe.Pointer(d))
if got := cfDataToBytes(d); !bytes.Equal(got, want) {
t.Errorf("bytesToCFData -> cfDataToBytes\ngot %x\nwant %x", got, want)
}
}
func TestEncrypt(t *testing.T) {
key, err := Cred(testIssuer)
if err != nil {
t.Errorf("Cred: got %v, want nil err", err)
return
}
plaintext := []byte("Plain text to encrypt")
_, err = key.Encrypt(plaintext, crypto.SHA256)
if err != nil {
t.Errorf("Encrypt: got %v, want nil err", err)
return
}
}
func BenchmarkEncrypt(b *testing.B) {
key, err := Cred(testIssuer)
if err != nil {
b.Errorf("Cred: got %v, want nil err", err)
return
}
plaintext := []byte("Plain text to encrypt")
for i := 0; i < b.N; i++ {
_, err := key.Encrypt(plaintext, crypto.SHA256)
if err != nil {
b.Errorf("Encrypt: got %v, want nil err", err)
}
}
}
func TestDecrypt(t *testing.T) {
key, err := Cred(testIssuer)
if err != nil {
t.Errorf("Cred: got %v, want nil err", err)
return
}
byteSlice := []byte("Plain text to encrypt")
ciphertext, _ := key.Encrypt(byteSlice, crypto.SHA256)
plaintext, err := key.Decrypt(ciphertext, &rsa.OAEPOptions{Hash: crypto.SHA256})
if err != nil {
t.Errorf("Decrypt: got %v, want nil err", err)
return
}
if !bytes.Equal(byteSlice, plaintext) {
t.Errorf("Decryption message does not match original: got %v, want %v", plaintext, byteSlice)
}
}
func BenchmarkDecrypt(b *testing.B) {
key, err := Cred(testIssuer)
if err != nil {
b.Errorf("Cred: got %v, want nil err", err)
return
}
byteSlice := []byte("Plain text to encrypt")
ciphertext, _ := key.Encrypt(byteSlice, crypto.SHA256)
for i := 0; i < b.N; i++ {
_, err := key.Decrypt(ciphertext, &rsa.OAEPOptions{Hash: crypto.SHA256})
if err != nil {
b.Errorf("Decrypt: got %v, want nil err", err)
}
}
}
enterprise-certificate-proxy-0.3.4/internal/signer/darwin/signer.go 0000664 0000000 0000000 00000011474 14667627643 0025576 0 ustar 00root root 0000000 0000000 // Copyright 2022 Google LLC.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Signer.go is a net/rpc server that listens on stdin/stdout, exposing
// methods that perform device certificate signing for Mac OS using keychain utils.
// This server is intended to be launched as a subprocess by the signer client,
// and should not be launched manually as a stand-alone process.
package main
import (
"crypto"
"crypto/rsa"
"crypto/x509"
"encoding/gob"
"io"
"log"
"net/rpc"
"os"
"time"
"github.com/googleapis/enterprise-certificate-proxy/internal/signer/darwin/keychain"
"github.com/googleapis/enterprise-certificate-proxy/internal/signer/util"
)
// If ECP Logging is enabled return true
// Otherwise return false
func enableECPLogging() bool {
if os.Getenv("ENABLE_ENTERPRISE_CERTIFICATE_LOGS") != "" {
return true
}
log.SetOutput(io.Discard)
return false
}
func init() {
gob.Register(crypto.SHA256)
gob.Register(crypto.SHA384)
gob.Register(crypto.SHA512)
gob.Register(&rsa.PSSOptions{})
gob.Register(&rsa.OAEPOptions{})
}
// SignArgs contains arguments for a Sign API call.
type SignArgs struct {
Digest []byte // The content to sign.
Opts crypto.SignerOpts // Options for signing. Must implement HashFunc().
}
// EncryptArgs contains arguments for an Encrypt API call.
type EncryptArgs struct {
Plaintext []byte // The plaintext to encrypt.
Opts any // Options for encryption. Ex: an instance of crypto.Hash.
}
// DecryptArgs contains arguments to for a Decrypt API call.
type DecryptArgs struct {
Ciphertext []byte // The ciphertext to decrypt.
Opts crypto.DecrypterOpts // Options for decryption. Ex: an instance of *rsa.OAEPOptions.
}
// A EnterpriseCertSigner exports RPC methods for signing.
type EnterpriseCertSigner struct {
key *keychain.Key
}
// A Connection wraps a pair of unidirectional streams as an io.ReadWriteCloser.
type Connection struct {
io.ReadCloser
io.WriteCloser
}
// Close closes c's underlying ReadCloser and WriteCloser.
func (c *Connection) Close() error {
rerr := c.ReadCloser.Close()
werr := c.WriteCloser.Close()
if rerr != nil {
return rerr
}
return werr
}
// CertificateChain returns the credential as a raw X509 cert chain. This
// contains the public key.
func (k *EnterpriseCertSigner) CertificateChain(ignored struct{}, certificateChain *[][]byte) error {
*certificateChain = k.key.CertificateChain()
return nil
}
// Public returns the corresponding public key for this Key, in ASN.1 DER form.
func (k *EnterpriseCertSigner) Public(ignored struct{}, publicKey *[]byte) (err error) {
*publicKey, err = x509.MarshalPKIXPublicKey(k.key.Public())
return
}
// Sign signs a message digest. Stores result in "resp".
func (k *EnterpriseCertSigner) Sign(args SignArgs, resp *[]byte) (err error) {
*resp, err = k.key.Sign(nil, args.Digest, args.Opts)
return
}
// Encrypt encrypts a plaintext message digest. Stores result in "resp".
func (k *EnterpriseCertSigner) Encrypt(args EncryptArgs, resp *[]byte) (err error) {
*resp, err = k.key.Encrypt(args.Plaintext, args.Opts)
return
}
// Decrypt decrypts a ciphertext message digest. Stores result in "resp".
func (k *EnterpriseCertSigner) Decrypt(args DecryptArgs, resp *[]byte) (err error) {
*resp, err = k.key.Decrypt(args.Ciphertext, args.Opts)
return
}
func main() {
enableECPLogging()
if len(os.Args) != 2 {
log.Fatalln("Signer is not meant to be invoked manually, exiting...")
}
configFilePath := os.Args[1]
config, err := util.LoadConfig(configFilePath)
if err != nil {
log.Fatalf("Failed to load enterprise cert config: %v", err)
}
enterpriseCertSigner := new(EnterpriseCertSigner)
enterpriseCertSigner.key, err = keychain.Cred(config.CertConfigs.MacOSKeychain.Issuer)
if err != nil {
log.Fatalf("Failed to initialize enterprise cert signer using keychain: %v", err)
}
if err := rpc.Register(enterpriseCertSigner); err != nil {
log.Fatalf("Failed to register enterprise cert signer with net/rpc: %v", err)
}
// If the parent process dies, we should exit.
// We can detect this by periodically checking if the PID of the parent
// process is 1 (https://stackoverflow.com/a/2035683).
go func() {
for {
if os.Getppid() == 1 {
log.Fatalln("Enterprise cert signer's parent process died, exiting...")
}
time.Sleep(time.Second)
}
}()
rpc.ServeConn(&Connection{os.Stdin, os.Stdout})
}
enterprise-certificate-proxy-0.3.4/internal/signer/linux/ 0000775 0000000 0000000 00000000000 14667627643 0023624 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/internal/signer/linux/pkcs11/ 0000775 0000000 0000000 00000000000 14667627643 0024726 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/internal/signer/linux/pkcs11/pkcs11.go 0000664 0000000 0000000 00000014215 14667627643 0026362 0 ustar 00root root 0000000 0000000 // Copyright 2022 Google LLC.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// pkcs11 provides helpers for working with certificates via PKCS#11 APIs
// provided by go-pkcs11
package pkcs11
import (
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"errors"
"fmt"
"hash"
"io"
"strconv"
"strings"
"github.com/google/go-pkcs11/pkcs11"
)
// ParseHexString parses hexadecimal string into uint32
func ParseHexString(str string) (i uint32, err error) {
stripped := strings.Replace(str, "0x", "", -1)
resultUint64, err := strconv.ParseUint(stripped, 16, 32)
if err != nil {
return 0, err
}
return uint32(resultUint64), nil
}
// Cred returns a Key wrapping the first valid certificate in the pkcs11 module
// matching a given slot and label.
func Cred(pkcs11Module string, slotUint32Str string, label string, userPin string) (*Key, error) {
module, err := pkcs11.Open(pkcs11Module)
if err != nil {
return nil, err
}
slotUint32, err := ParseHexString(slotUint32Str)
if err != nil {
return nil, err
}
kslot, err := module.Slot(slotUint32, pkcs11.Options{PIN: userPin})
if err != nil {
return nil, err
}
certs, err := kslot.Objects(pkcs11.Filter{Class: pkcs11.ClassCertificate, Label: label})
if err != nil {
return nil, err
}
if len(certs) < 1 {
return nil, fmt.Errorf("No certificate object was found with label %s.", label)
}
cert, err := certs[0].Certificate()
if err != nil {
return nil, err
}
x509, err := cert.X509()
if err != nil {
return nil, err
}
var kchain [][]byte
kchain = append(kchain, x509.Raw)
pubKeys, err := kslot.Objects(pkcs11.Filter{Class: pkcs11.ClassPublicKey, Label: label})
if err != nil {
return nil, err
}
if len(pubKeys) < 1 {
return nil, fmt.Errorf("No public key object was found with label %s.", label)
}
pubKey, err := pubKeys[0].PublicKey()
if err != nil {
return nil, err
}
privkeys, err := kslot.Objects(pkcs11.Filter{Class: pkcs11.ClassPrivateKey, Label: label})
if err != nil {
return nil, err
}
if len(privkeys) < 1 {
return nil, fmt.Errorf("No private key object was found with label %s.", label)
}
privKey, err := privkeys[0].PrivateKey(pubKey)
if err != nil {
return nil, err
}
ksigner, ok := privKey.(crypto.Signer)
if !ok {
return nil, errors.New("PrivateKey does not implement crypto.Signer")
}
kdecrypter, _ := privKey.(crypto.Decrypter)
defaultHash := crypto.SHA256
return &Key{
slot: kslot,
signer: ksigner,
chain: kchain,
privKey: privKey,
label: label,
module: *module,
hash: defaultHash,
decrypter: kdecrypter,
}, nil
}
// Key is a wrapper around the pkcs11 module and uses it to
// implement signing-related methods.
type Key struct {
slot *pkcs11.Slot
signer crypto.Signer
chain [][]byte
privKey crypto.PrivateKey
label string
module pkcs11.Module
hash crypto.Hash
decrypter crypto.Decrypter
}
// CertificateChain returns the credential as a raw X509 cert chain. This
// contains the public key.
func (k *Key) CertificateChain() [][]byte {
return k.chain
}
// Close releases resources held by the credential.
func (k *Key) Close() {
k.slot.Close()
k.module.Close()
}
// Public returns the corresponding public key for this Key.
func (k *Key) Public() crypto.PublicKey {
return k.signer.Public()
}
// Sign signs a message.
func (k *Key) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
return k.signer.Sign(nil, digest, opts)
}
// Encrypt encrypts a plaintext message digest using the public key. Here, we use standard golang API.
func (k *Key) Encrypt(plaintext []byte, opts any) ([]byte, error) {
if hash, ok := opts.(crypto.Hash); ok {
k.hash = hash
} else {
return nil, fmt.Errorf("Unsupported encrypt opts: %v", opts)
}
publicKey := k.Public()
_, ok := publicKey.(*rsa.PublicKey)
if ok {
return k.encryptRSA(plaintext)
}
_, ok = publicKey.(*ecdsa.PublicKey)
if ok {
// TODO: Implement encryption for ec keys - https://github.com/googleapis/enterprise-certificate-proxy/issues/95
return nil, errors.New("encrypt error: EC keys not yet supported")
}
return nil, errors.New("encrypt error: Unsupported key type")
}
// Decrypt decrypts a ciphertext message digest using the private key. Here, we pass off the decryption to pkcs11 library.
func (k *Key) Decrypt(msg []byte, opts crypto.DecrypterOpts) ([]byte, error) {
if oaepOpts, ok := opts.(*rsa.OAEPOptions); ok {
k.hash = oaepOpts.Hash
} else {
return nil, fmt.Errorf("Unsupported DecrypterOpts: %v", opts)
}
if k.decrypter == nil {
return nil, fmt.Errorf("decrypt error: Decrypter is nil")
}
publicKey := k.Public()
_, ok := publicKey.(*rsa.PublicKey)
if ok {
return k.decryptRSAWithPKCS11(msg)
}
_, ok = publicKey.(*ecdsa.PublicKey)
if ok {
// TODO: Implement decryption for ec keys - https://github.com/googleapis/enterprise-certificate-proxy/issues/95
return nil, errors.New("decrypt error: EC keys not yet supported")
}
return nil, errors.New("decrypt error: Unsupported key type")
}
func (k *Key) encryptRSA(data []byte) ([]byte, error) {
publicKey := k.Public()
rsaPubKey := publicKey.(*rsa.PublicKey)
hash, err := cryptoHashToHash(k.hash)
if err != nil {
return nil, err
}
return rsa.EncryptOAEP(hash, rand.Reader, rsaPubKey, data, nil)
}
func (k *Key) decryptRSAWithPKCS11(encryptedData []byte) ([]byte, error) {
opts := &rsa.OAEPOptions{Hash: k.hash}
return k.decrypter.Decrypt(nil, encryptedData, opts)
}
func cryptoHashToHash(hash crypto.Hash) (hash.Hash, error) {
switch hash {
case crypto.SHA256:
return sha256.New(), nil
case crypto.SHA1:
return sha1.New(), nil
default:
return nil, errors.New("hash conversion error: Unsupported hash")
}
}
enterprise-certificate-proxy-0.3.4/internal/signer/linux/pkcs11/pkcs11_test.go 0000664 0000000 0000000 00000005511 14667627643 0027420 0 ustar 00root root 0000000 0000000 // Copyright 2023 Google LLC.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pkcs11
import (
"bytes"
"crypto"
"crypto/rsa"
"flag"
"testing"
)
const (
testModule = "/usr/lib/softhsm/libsofthsm2.so"
testLabel = "Demo Object"
testUserPin = "0000"
)
var testSlot = flag.String("testSlot", "", "libsofthsm2 slot location")
func makeTestKey() (*Key, error) {
key, err := Cred(testModule, *testSlot, testLabel, testUserPin)
return key, err
}
func TestParseHexString(t *testing.T) {
got, err := ParseHexString("0x1739427")
if err != nil {
t.Fatalf("ParseHexString error: %v", err)
}
want := uint32(0x1739427)
if got != want {
t.Errorf("Expected result is %v, got: %v", want, got)
}
}
func TestParseHexStringFailure(t *testing.T) {
_, err := ParseHexString("abcdefgh")
if err == nil {
t.Error("Expected error but got nil")
}
}
func TestCredLinux(t *testing.T) {
key, err := makeTestKey()
if err != nil {
t.Errorf("Cred error: %q", err)
}
defer key.Close()
}
func BenchmarkEncryptRSA(b *testing.B) {
msg := "Plain text to encrypt"
bMsg := []byte(msg)
key, errCred := makeTestKey()
if errCred != nil {
b.Errorf("Cred error: %q", errCred)
return
}
defer key.Close()
b.Run("encryptRSA Crypto", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, errEncrypt := key.encryptRSA(bMsg)
if errEncrypt != nil {
b.Errorf("EncryptRSA error: %q", errEncrypt)
return
}
}
})
}
func TestEncrypt(t *testing.T) {
key, errCred := makeTestKey()
if errCred != nil {
t.Errorf("Cred error: %q", errCred)
return
}
defer key.Close()
msg := "Plain text to encrypt"
bMsg := []byte(msg)
_, err := key.Encrypt(bMsg, crypto.SHA1)
if err != nil {
t.Errorf("Encrypt error: %q", err)
}
}
func TestDecrypt(t *testing.T) {
key, errCred := makeTestKey()
if errCred != nil {
t.Errorf("Cred error: %q", errCred)
return
}
defer key.Close()
msg := "Plain text to encrypt"
bMsg := []byte(msg)
// Softhsm only supports SHA1
ciphertext, err := key.Encrypt(bMsg, crypto.SHA1)
if err != nil {
t.Errorf("Encrypt error: %q", err)
}
decrypted, err := key.Decrypt(ciphertext, &rsa.OAEPOptions{Hash: crypto.SHA1})
if err != nil {
t.Fatalf("Decrypt error: %v", err)
}
decrypted = bytes.Trim(decrypted, "\x00")
if string(decrypted) != msg {
t.Errorf("Decrypt error: expected %q, got %q", msg, string(decrypted))
}
}
enterprise-certificate-proxy-0.3.4/internal/signer/linux/signer.go 0000664 0000000 0000000 00000011617 14667627643 0025450 0 ustar 00root root 0000000 0000000 // Copyright 2022 Google LLC.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Signer.go is a net/rpc server that listens on stdin/stdout, exposing
// methods that perform device certificate signing for Linux using PKCS11
// shared library.
// This server is intended to be launched as a subprocess by the signer client,
// and should not be launched manually as a stand-alone process.
package main
import (
"crypto"
"crypto/rsa"
"crypto/x509"
"encoding/gob"
"io"
"log"
"net/rpc"
"os"
"time"
"github.com/googleapis/enterprise-certificate-proxy/internal/signer/linux/pkcs11"
"github.com/googleapis/enterprise-certificate-proxy/internal/signer/util"
)
// If ECP Logging is enabled return true
// Otherwise return false
func enableECPLogging() bool {
if os.Getenv("ENABLE_ENTERPRISE_CERTIFICATE_LOGS") != "" {
return true
}
log.SetOutput(io.Discard)
return false
}
func init() {
gob.Register(crypto.SHA256)
gob.Register(crypto.SHA384)
gob.Register(crypto.SHA512)
gob.Register(&rsa.PSSOptions{})
gob.Register(&rsa.OAEPOptions{})
}
// SignArgs contains arguments for a Sign API call.
type SignArgs struct {
Digest []byte // The content to sign.
Opts crypto.SignerOpts // Options for signing. Must implement HashFunc().
}
// EncryptArgs contains arguments for an Encrypt API call.
type EncryptArgs struct {
Plaintext []byte // The plaintext to encrypt.
Opts any // Options for encryption. Ex: an instance of crypto.Hash.
}
// DecryptArgs contains arguments to for a Decrypt API call.
type DecryptArgs struct {
Ciphertext []byte // The ciphertext to decrypt.
Opts crypto.DecrypterOpts // Options for decryption. Ex: an instance of *rsa.OAEPOptions.
}
// A EnterpriseCertSigner exports RPC methods for signing.
type EnterpriseCertSigner struct {
key *pkcs11.Key
}
// A Connection wraps a pair of unidirectional streams as an io.ReadWriteCloser.
type Connection struct {
io.ReadCloser
io.WriteCloser
}
// Close closes c's underlying ReadCloser and WriteCloser.
func (c *Connection) Close() error {
rerr := c.ReadCloser.Close()
werr := c.WriteCloser.Close()
if rerr != nil {
return rerr
}
return werr
}
// CertificateChain returns the credential as a raw X509 cert chain. This
// contains the public key.
func (k *EnterpriseCertSigner) CertificateChain(ignored struct{}, certificateChain *[][]byte) (err error) {
*certificateChain = k.key.CertificateChain()
return nil
}
// Public returns the corresponding public key for this Key, in ASN.1 DER form.
func (k *EnterpriseCertSigner) Public(ignored struct{}, publicKey *[]byte) (err error) {
*publicKey, err = x509.MarshalPKIXPublicKey(k.key.Public())
return
}
// Sign signs a message digest. Stores result in "resp".
func (k *EnterpriseCertSigner) Sign(args SignArgs, resp *[]byte) (err error) {
*resp, err = k.key.Sign(nil, args.Digest, args.Opts)
return
}
// Encrypt encrypts a plaintext msg. Stores result in "resp".
func (k *EnterpriseCertSigner) Encrypt(args EncryptArgs, resp *[]byte) (err error) {
*resp, err = k.key.Encrypt(args.Plaintext, args.Opts)
return
}
// Decrypt decrypts a ciphertext msg. Stores result in "resp".
func (k *EnterpriseCertSigner) Decrypt(args DecryptArgs, resp *[]byte) (err error) {
*resp, err = k.key.Decrypt(args.Ciphertext, args.Opts)
return
}
func main() {
enableECPLogging()
if len(os.Args) != 2 {
log.Fatalln("Signer is not meant to be invoked manually, exiting...")
}
configFilePath := os.Args[1]
config, err := util.LoadConfig(configFilePath)
if err != nil {
log.Fatalf("Failed to load enterprise cert config: %v", err)
}
enterpriseCertSigner := new(EnterpriseCertSigner)
enterpriseCertSigner.key, err = pkcs11.Cred(config.CertConfigs.PKCS11.PKCS11Module, config.CertConfigs.PKCS11.Slot, config.CertConfigs.PKCS11.Label, config.CertConfigs.PKCS11.UserPin)
if err != nil {
log.Fatalf("Failed to initialize enterprise cert signer using pkcs11: %v", err)
}
if err := rpc.Register(enterpriseCertSigner); err != nil {
log.Fatalf("Failed to register enterprise cert signer with net/rpc: %v", err)
}
// If the parent process dies, we should exit.
// We can detect this by periodically checking if the PID of the parent
// process is 1 (https://stackoverflow.com/a/2035683).
go func() {
for {
if os.Getppid() == 1 {
log.Fatalln("Enterprise cert signer's parent process died, exiting...")
}
time.Sleep(time.Second)
}
}()
rpc.ServeConn(&Connection{os.Stdin, os.Stdout})
}
enterprise-certificate-proxy-0.3.4/internal/signer/test/ 0000775 0000000 0000000 00000000000 14667627643 0023444 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/internal/signer/test/signer.go 0000664 0000000 0000000 00000007026 14667627643 0025267 0 ustar 00root root 0000000 0000000 // Copyright 2022 Google LLC.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// signer.go is a net/rpc server that listens on stdin/stdout, exposing
// mock methods for testing client.go.
package main
import (
"crypto"
"crypto/tls"
"crypto/x509"
"io"
"log"
"net/rpc"
"os"
"time"
)
// SignArgs encapsulate the parameters for the Sign method.
type SignArgs struct {
Digest []byte
Opts crypto.SignerOpts
}
// EncryptArgs encapsulate the parameters for the Encrypt method.
type EncryptArgs struct {
Plaintext []byte
}
// DecryptArgs encapsulate the parameters for the Decrypt method.
type DecryptArgs struct {
Ciphertext []byte
}
// EnterpriseCertSigner exports RPC methods for signing.
type EnterpriseCertSigner struct {
cert *tls.Certificate
}
// Connection wraps a pair of unidirectional streams as an io.ReadWriteCloser.
type Connection struct {
io.ReadCloser
io.WriteCloser
}
// Close closes c's underlying ReadCloser and WriteCloser.
func (c *Connection) Close() error {
rerr := c.ReadCloser.Close()
werr := c.WriteCloser.Close()
if rerr != nil {
return rerr
}
return werr
}
// CertificateChain returns the credential as a raw X509 cert chain. This
// contains the public key.
func (k *EnterpriseCertSigner) CertificateChain(ignored struct{}, certificateChain *[][]byte) error {
*certificateChain = k.cert.Certificate
return nil
}
// Public returns the first public key for this Key, in ASN.1 DER form.
func (k *EnterpriseCertSigner) Public(ignored struct{}, publicKey *[]byte) (err error) {
if len(k.cert.Certificate) == 0 {
return nil
}
cert, err := x509.ParseCertificate(k.cert.Certificate[0])
if err != nil {
return err
}
*publicKey, err = x509.MarshalPKIXPublicKey(cert.PublicKey)
return err
}
// Sign signs a message digest. For testing, we return the input as-is.
func (k *EnterpriseCertSigner) Sign(args SignArgs, resp *[]byte) (err error) {
*resp = args.Digest
return nil
}
// Encrypt encrypts a plaintext msg. For testing, we return the input as-is.
func (k *EnterpriseCertSigner) Encrypt(args EncryptArgs, plaintext *[]byte) (err error) {
*plaintext = args.Plaintext
return nil
}
// Decrypt decrypts a ciphertext msg. For testing, we return the input as-is.
func (k *EnterpriseCertSigner) Decrypt(args DecryptArgs, ciphertext *[]byte) (err error) {
*ciphertext = args.Ciphertext
return nil
}
func main() {
enterpriseCertSigner := new(EnterpriseCertSigner)
data, err := os.ReadFile(os.Args[1])
if err != nil {
log.Fatalf("Error reading certificate: %v", err)
}
cert, _ := tls.X509KeyPair(data, data)
enterpriseCertSigner.cert = &cert
if err := rpc.Register(enterpriseCertSigner); err != nil {
log.Fatalf("Error registering net/rpc: %v", err)
}
// If the parent process dies, we should exit.
// We can detect this by periodically checking if the PID of the parent
// process is 1 (https://stackoverflow.com/a/2035683).
go func() {
for {
if os.Getppid() == 1 {
log.Fatalln("Parent process died, exiting...")
}
time.Sleep(time.Second)
}
}()
rpc.ServeConn(&Connection{os.Stdin, os.Stdout})
}
enterprise-certificate-proxy-0.3.4/internal/signer/util/ 0000775 0000000 0000000 00000000000 14667627643 0023442 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/internal/signer/util/test_data/ 0000775 0000000 0000000 00000000000 14667627643 0025412 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/internal/signer/util/test_data/certificate_config.json 0000664 0000000 0000000 00000000561 14667627643 0032116 0 ustar 00root root 0000000 0000000 {
"cert_configs": {
"macos_keychain": {
"issuer": "Google Endpoint Verification"
},
"windows_store": {
"issuer": "enterprise_v1_corp_client",
"store": "MY",
"provider": "current_user"
},
"pkcs11": {
"slot": "0x1739427",
"label": "gecc",
"user_pin": "0000",
"module": "pkcs11_module.so"
}
}
}
enterprise-certificate-proxy-0.3.4/internal/signer/util/util.go 0000664 0000000 0000000 00000004634 14667627643 0024755 0 ustar 00root root 0000000 0000000 // Copyright 2022 Google LLC.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package util provides helper functions for the signer.
package util
import (
"encoding/json"
"io"
"os"
)
// EnterpriseCertificateConfig contains parameters for initializing signer.
type EnterpriseCertificateConfig struct {
CertConfigs CertConfigs `json:"cert_configs"`
}
// CertConfigs is a container for various OS-specific ECP Configs.
type CertConfigs struct {
MacOSKeychain MacOSKeychain `json:"macos_keychain"`
WindowsStore WindowsStore `json:"windows_store"`
PKCS11 PKCS11 `json:"pkcs11"`
}
// MacOSKeychain contains keychain parameters describing the certificate to use.
type MacOSKeychain struct {
Issuer string `json:"issuer"`
}
// WindowsStore contains Windows key store parameters describing the certificate to use.
type WindowsStore struct {
Issuer string `json:"issuer"`
Store string `json:"store"`
Provider string `json:"provider"`
}
// PKCS11 contains PKCS#11 parameters describing the certificate to use.
type PKCS11 struct {
Slot string `json:"slot"` // The hexadecimal representation of the uint36 slot ID. (ex:0x1739427)
Label string `json:"label"` // The token label (ex: gecc)
PKCS11Module string `json:"module"` // The path to the pkcs11 module (shared lib)
UserPin string `json:"user_pin"` // Optional user pin to unlock the PKCS #11 module. If it is not defined or empty C_Login will not be called.
}
// LoadConfig retrieves the ECP config file.
func LoadConfig(configFilePath string) (config EnterpriseCertificateConfig, err error) {
jsonFile, err := os.Open(configFilePath)
if err != nil {
return EnterpriseCertificateConfig{}, err
}
byteValue, err := io.ReadAll(jsonFile)
if err != nil {
return EnterpriseCertificateConfig{}, err
}
err = json.Unmarshal(byteValue, &config)
if err != nil {
return EnterpriseCertificateConfig{}, err
}
return config, nil
}
enterprise-certificate-proxy-0.3.4/internal/signer/util/util_test.go 0000664 0000000 0000000 00000004437 14667627643 0026015 0 ustar 00root root 0000000 0000000 // Copyright 2022 Google LLC.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"testing"
)
func TestLoadConfig(t *testing.T) {
config, err := LoadConfig("./test_data/certificate_config.json")
// darwin
if err != nil {
t.Fatalf("LoadConfig error: %q", err)
}
want := "Google Endpoint Verification"
if config.CertConfigs.MacOSKeychain.Issuer != want {
t.Errorf("Expected issuer is %q, got: %q", want, config.CertConfigs.MacOSKeychain.Issuer)
}
// windows
want = "enterprise_v1_corp_client"
if config.CertConfigs.WindowsStore.Issuer != want {
t.Errorf("Expected issuer is %q, got: %q", want, config.CertConfigs.WindowsStore.Issuer)
}
want = "MY"
if config.CertConfigs.WindowsStore.Store != want {
t.Errorf("Expected store is %q, got: %q", want, config.CertConfigs.WindowsStore.Store)
}
want = "current_user"
if config.CertConfigs.WindowsStore.Provider != want {
t.Errorf("Expected provider is %q, got: %q", want, config.CertConfigs.WindowsStore.Provider)
}
// pkcs11
want = "0x1739427"
if config.CertConfigs.PKCS11.Slot != want {
t.Errorf("Expected slot is %v, got: %v", want, config.CertConfigs.PKCS11.Slot)
}
want = "gecc"
if config.CertConfigs.PKCS11.Label != want {
t.Errorf("Expected label is %v, got: %v", want, config.CertConfigs.PKCS11.Label)
}
want = "pkcs11_module.so"
if config.CertConfigs.PKCS11.PKCS11Module != want {
t.Errorf("Expected pkcs11_module is %v, got: %v", want, config.CertConfigs.PKCS11.PKCS11Module)
}
want = "0000"
if config.CertConfigs.PKCS11.UserPin != want {
t.Errorf("Expected user pin is %v, got: %v", want, config.CertConfigs.PKCS11.UserPin)
}
}
func TestLoadConfigMissing(t *testing.T) {
_, err := LoadConfig("./test_data/certificate_config_missing.json")
if err == nil {
t.Error("Expected error but got nil")
}
}
enterprise-certificate-proxy-0.3.4/internal/signer/windows/ 0000775 0000000 0000000 00000000000 14667627643 0024157 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/internal/signer/windows/.gitattributes 0000664 0000000 0000000 00000000020 14667627643 0027042 0 ustar 00root root 0000000 0000000 *.go text eol=lf enterprise-certificate-proxy-0.3.4/internal/signer/windows/ncrypt/ 0000775 0000000 0000000 00000000000 14667627643 0025476 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.3.4/internal/signer/windows/ncrypt/cert_util.go 0000664 0000000 0000000 00000025500 14667627643 0030021 0 ustar 00root root 0000000 0000000 // Copyright 2022 Google LLC.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build windows
// +build windows
// Cert_util provides helpers for working with Windows certificates via crypt32.dll
package ncrypt
import (
"crypto"
"crypto/x509"
"errors"
"fmt"
"io"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
const (
// wincrypt.h constants
encodingX509ASN = 1 // X509_ASN_ENCODING
certStoreCurrentUserID = 1 // CERT_SYSTEM_STORE_CURRENT_USER_ID
certStoreLocalMachineID = 2 // CERT_SYSTEM_STORE_LOCAL_MACHINE_ID
infoIssuerFlag = 4 // CERT_INFO_ISSUER_FLAG
compareNameStrW = 8 // CERT_COMPARE_NAME_STR_A
certStoreProvSystem = 10 // CERT_STORE_PROV_SYSTEM
compareShift = 16 // CERT_COMPARE_SHIFT
locationShift = 16 // CERT_SYSTEM_STORE_LOCATION_SHIFT
findIssuerStr = compareNameStrW<