pax_global_header 0000666 0000000 0000000 00000000064 14315151263 0014513 g ustar 00root root 0000000 0000000 52 comment=c5f65f94cd903cc16bba58964863229eb35ec41c
enterprise-certificate-proxy-0.2.0/ 0000775 0000000 0000000 00000000000 14315151263 0017331 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/.github/ 0000775 0000000 0000000 00000000000 14315151263 0020671 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14315151263 0022726 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/.github/workflows/test-client.yml 0000664 0000000 0000000 00000000606 14315151263 0025706 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@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18
- name: Build
run: go build -v ./client/...
- name: Test
run: go test -v ./client/...
enterprise-certificate-proxy-0.2.0/.github/workflows/test-cshared.yml 0000664 0000000 0000000 00000000663 14315151263 0026044 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@v3
- name: Set up Go
uses: actions/setup-go@v3
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.2.0/.github/workflows/test-signer-darwin.yml 0000664 0000000 0000000 00000000742 14315151263 0027202 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@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18
- name: Build
working-directory: ./internal/signer/darwin
run: go build -v ./...
- name: Test
working-directory: ./internal/signer/darwin
run: go test -v ./...
enterprise-certificate-proxy-0.2.0/.github/workflows/test-signer-linux.yml 0000664 0000000 0000000 00000000740 14315151263 0027053 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@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18
- name: Build
working-directory: ./internal/signer/linux
run: go build -v ./...
- name: Test
working-directory: ./internal/signer/linux
run: go test -v ./...
enterprise-certificate-proxy-0.2.0/.github/workflows/test-signer-windows.yml 0000664 0000000 0000000 00000000747 14315151263 0027415 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@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18
- name: Build
working-directory: ./internal/signer/windows
run: go build -v ./...
- name: Test
working-directory: ./internal/signer/windows
run: go test -v ./...
enterprise-certificate-proxy-0.2.0/.gitignore 0000664 0000000 0000000 00000000064 14315151263 0021321 0 ustar 00root root 0000000 0000000 # MacOS
.DS_Store
# compiled binaries
build/bin/**
enterprise-certificate-proxy-0.2.0/CODEOWNERS 0000664 0000000 0000000 00000000105 14315151263 0020720 0 ustar 00root root 0000000 0000000 # Default owner for all directories not owned by others
* @andyrzhao
enterprise-certificate-proxy-0.2.0/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000010627 14315151263 0022136 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.2.0/CONTRIBUTING.md 0000664 0000000 0000000 00000002111 14315151263 0021555 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.2.0/LICENSE 0000664 0000000 0000000 00000026136 14315151263 0020346 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.2.0/README.md 0000664 0000000 0000000 00000011716 14315151263 0020616 0 ustar 00root root 0000000 0000000 # Google Proxies for Enterprise Certificates (Preview)
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
Currently ECP is in Preview stage and all the APIs and configurations are **subject to change**.
The following platforms/keystores are supported by ECP:
- MacOS: __Keychain__
- Windows: __MyStore__
- Linux: __PKCS#11__
## Quick Start Guide
### Prerequisites
Before using ECP with your application/client, you should follow the instructions [here](enterprisecert) to configure your enterprise certificate policies with Access Context Manager.
### Installation
1. Install Openssl
`brew install openssl@1.1`
1. Install gcloud CLI (Cloud SDK) at: https://cloud.google.com/sdk/docs/install
1. Download the ECP binary based on your OS from the latest [Github release](https://github.com/googleapis/enterprise-certificate-proxy/releases).
1. Unzip the downloaded zip and move all the binaries into the following directory:
1. Windows: `%AppData%/gcloud/enterprise_cert`.
1. Linux/MacOS: `~/.config/gcloud/enterprise_cert`.
1. If using gcloud’s bundled Python, skip to the next step. If not, install pyopenssl==22.0.0 and cryptography==36.0.2
1. pip install cryptography==36.0.2
1. pip install pyopenssl==22.0.0
1. Create a new JSON file at `.config/gcloud/certificate_config.json`.
1. Alternatively you can put the JSON in the location of your choice and set the path to it using `$ gcloud config set context_aware/enterprise_certificate_config_file_path ""`.
1. Another approach for setting the JSON file location is setting the location with the `GOOGLE_API_CERTIFICATE_CONFIG` environment variable.
1. Update the `certificate_config.json` file with details about the certificate (See [Configuration](#configutation) section for details.)
1. Enable usage of client certificates through gcloud CLI config command:
```
gcloud config set context_aware/use_client_certificate true
```
### Configuration
ECP relies on the `certificate_config.json` file to read all the metadata information of locating the certificate. The contents of this JSON file looks like the following:
#### MacOS (Keychain)
```json
{
"cert_configs": {
"macos_keychain": {
"issuer": "YOUR_CERT_ISSUER",
},
},
"libs": {
"ecp": "~/.config/gcloud/enterprise_cert/ecp",
"ecp_client": "~/.config/gcloud/enterprise_cert/libecp.dylib",
"tls_offload": "~/.config/gcloud/enterprise_cert/libtls_offload.dylib",
},
"version": 1,
}
```
#### Windows (MyStore)
```json
{
"cert_configs": {
"windows_my_store": {
"store": "MY",
"provider": "current_user",
"issuer": "YOUR_CERT_ISSUER",
},
},
"libs": {
"ecp": "%AppData%/gcloud/enterprise_cert/ecp.exe",
"ecp_client": "%AppData%/gcloud/enterprise_cert/libecp.dll",
"tls_offload": "%AppData%/gcloud/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": "~/.config/gcloud/enterprise_cert/ecp",
"ecp_client": "~/.config/gcloud/enterprise_cert/libecp.so",
"tls_offload": "~/.config/gcloud/enterprise_cert/libtls_offload.so",
},
"version": 1,
}
```
## Build binaries
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 `powershell.exe .\build\scripts\windows_amd64.sh`. The binaries will be placed in `build\bin\windows_amd64` folder.
## Contributing
Contributions to this library are always welcome and highly encouraged. See the [CONTRIBUTING](contributing) documentation for more information on how to get started.
## License
Apache - See [LICENSE](license) for more information.
[zerotrust]: https://cloud.google.com/beyondcorp
[clientcert]: https://en.wikipedia.org/wiki/Client_certificate
[keystore]: https://en.wikipedia.org/wiki/Key_management
[cloudsdk]: https://cloud.google.com/sdk
[contributing]: ./CONTRIBUTING.md
[license]:./LICENSE.md
[enterprisecert]: https://cloud.google.com/access-context-manager/docs/enterprise-certificates
enterprise-certificate-proxy-0.2.0/SECURITY.md 0000664 0000000 0000000 00000000511 14315151263 0021117 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.2.0/build/ 0000775 0000000 0000000 00000000000 14315151263 0020430 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/build/scripts/ 0000775 0000000 0000000 00000000000 14315151263 0022117 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/build/scripts/darwin_amd64.sh 0000775 0000000 0000000 00000000620 14315151263 0024733 0 ustar 00root root 0000000 0000000 #!/bin/bash
set -eu
# 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 signer ./../../../build/bin/darwin_amd64/ecp
cd ./../../..
# Build the signer library
go build -buildmode=c-shared -o build/bin/darwin_amd64/libecp.dylib cshared/main.go
rm build/bin/darwin_amd64/libecp.h
enterprise-certificate-proxy-0.2.0/build/scripts/linux_amd64.sh 0000775 0000000 0000000 00000000576 14315151263 0024620 0 ustar 00root root 0000000 0000000 #!/bin/bash
# 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 -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 signer ./../../../build/bin/linux_amd64/ecp
cd ./../../..
enterprise-certificate-proxy-0.2.0/build/scripts/windows_amd64.ps1 0000664 0000000 0000000 00000001126 14315151263 0025231 0 ustar 00root root 0000000 0000000 $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 .\signer.exe ..\..\..\build\bin\windows_amd64\ecp.exe
Set-Location ..\..\..\
# Build the signer library
go build -buildmode=c-shared -o .\build\bin\windows_amd64\libecp.dll .\cshared\main.go
Remove-Item .\build\bin\windows_amd64\libecp.h
enterprise-certificate-proxy-0.2.0/client/ 0000775 0000000 0000000 00000000000 14315151263 0020607 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/client/client.go 0000664 0000000 0000000 00000012353 14315151263 0022420 0 ustar 00root root 0000000 0000000 // Copyright 2022 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// 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"
"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"
// 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(&rsa.PSSOptions{})
}
// SignArgs contains arguments to a crypto Signer.Sign method.
type SignArgs struct {
Digest []byte // The content to sign.
Opts crypto.SignerOpts // Options for signing, such as Hash identifier.
}
// 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)
}
if err := k.cmd.Wait(); err.Error() != "signal: killed" {
return fmt.Errorf("signer process was not killed: %w", err)
}
// 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 options.
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
}
// 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 == "" {
configFilePath = util.GetDefaultConfigFilePath()
}
enterpriseCertSignerPath, err := util.LoadSignerBinaryPath(configFilePath)
if err != nil {
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.2.0/client/client_test.go 0000664 0000000 0000000 00000004035 14315151263 0023455 0 ustar 00root root 0000000 0000000 // Copyright 2022 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// The tests in this file launches a mock signer binary "signer.go".
package client
import (
"bytes"
"crypto"
"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, os.ErrNotExist; !errors.Is(got, want) {
t.Errorf("Cred: with missing config; 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 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.2.0/client/testdata/ 0000775 0000000 0000000 00000000000 14315151263 0022420 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/client/testdata/certificate_config.json 0000664 0000000 0000000 00000000201 14315151263 0027113 0 ustar 00root root 0000000 0000000 {
"cert_configs": {
"test": {
"issuer": "Test Issuer"
}
},
"libs": {
"ecp": "./testdata/signer.sh"
}
}
enterprise-certificate-proxy-0.2.0/client/testdata/signer.sh 0000775 0000000 0000000 00000000321 14315151263 0024242 0 ustar 00root root 0000000 0000000 #!/bin/bash
# Copyright 2022 Google LLC.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
go run ../internal/signer/test/signer.go testdata/testcert.pem enterprise-certificate-proxy-0.2.0/client/testdata/testcert.pem 0000664 0000000 0000000 00000005601 14315151263 0024762 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.2.0/client/util/ 0000775 0000000 0000000 00000000000 14315151263 0021564 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/client/util/test_data/ 0000775 0000000 0000000 00000000000 14315151263 0023534 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/client/util/test_data/certificate_config.json 0000664 0000000 0000000 00000000141 14315151263 0030232 0 ustar 00root root 0000000 0000000 {
"libs": {
"ecp": "C:/Program Files (x86)/Google/Endpoint Verification/signer.exe"
}
}
enterprise-certificate-proxy-0.2.0/client/util/util.go 0000664 0000000 0000000 00000003365 14315151263 0023077 0 ustar 00root root 0000000 0000000 // Package util provides helper functions for the client.
package util
import (
"encoding/json"
"errors"
"io/ioutil"
"os"
"os/user"
"path/filepath"
"runtime"
)
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"`
}
// 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 {
return "", err
}
byteValue, err := ioutil.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 "", errors.New("signer binary path is missing")
}
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)
}
enterprise-certificate-proxy-0.2.0/client/util/util_test.go 0000664 0000000 0000000 00000000574 14315151263 0024135 0 ustar 00root root 0000000 0000000 package util
import (
"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)
}
}
enterprise-certificate-proxy-0.2.0/cshared/ 0000775 0000000 0000000 00000000000 14315151263 0020742 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/cshared/main.go 0000664 0000000 0000000 00000007123 14315151263 0022220 0 ustar 00root root 0000000 0000000 // 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"
"log"
"unsafe"
"github.com/googleapis/enterprise-certificate-proxy/client"
)
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 key.Close() != 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
}
// 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.
//
//export GetCertPemForPython
func GetCertPemForPython(configFilePath *C.char, certHolder *byte, certHolderLen int) int {
pemBytes := getCertPem(C.GoString(configFilePath))
if certHolder != nil {
cert := unsafe.Slice(certHolder, certHolderLen)
copy(cert, pemBytes)
}
return len(pemBytes)
}
// 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.
//
//export SignForPython
func SignForPython(configFilePath *C.char, digest *byte, digestLen int, sigHolder *byte, sigHolderLen int) int {
// First create a handle around the specified certificate and private key.
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 key.Close() != 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
}
// Create a Go buffer around the output buffer and copy the signature into the buffer
outBytes := unsafe.Slice(sigHolder, sigHolderLen)
if sigHolderLen < len(signature) {
log.Printf("The sigHolder buffer size %d is smaller than the signature size %d", sigHolderLen, len(signature))
return 0
}
for i := 0; i < len(signature); i++ {
outBytes[i] = signature[i]
}
return len(signature)
}
func main() {}
enterprise-certificate-proxy-0.2.0/go.mod 0000664 0000000 0000000 00000000103 14315151263 0020431 0 ustar 00root root 0000000 0000000 module github.com/googleapis/enterprise-certificate-proxy
go 1.18
enterprise-certificate-proxy-0.2.0/go.sum 0000664 0000000 0000000 00000000000 14315151263 0020452 0 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/internal/ 0000775 0000000 0000000 00000000000 14315151263 0021145 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/internal/signer/ 0000775 0000000 0000000 00000000000 14315151263 0022434 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/internal/signer/darwin/ 0000775 0000000 0000000 00000000000 14315151263 0023720 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/internal/signer/darwin/go.mod 0000664 0000000 0000000 00000000026 14315151263 0025024 0 ustar 00root root 0000000 0000000 module signer
go 1.18 enterprise-certificate-proxy-0.2.0/internal/signer/darwin/keychain/ 0000775 0000000 0000000 00000000000 14315151263 0025513 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/internal/signer/darwin/keychain/keychain.go 0000664 0000000 0000000 00000031770 14315151263 0027645 0 ustar 00root root 0000000 0000000 //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,
}
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,
}
)
// 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) *cfError {
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
}
// 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) (*Key, error) {
k := &Key{
privateKeyRef: privateKeyRef,
certs: certs,
}
// 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))
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))
})
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 := identityToSecKeyRef(leafIdent)
if err != nil {
return nil, err
}
defer C.CFRelease(C.CFTypeRef(skr))
return newKey(skr, certs)
}
// 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 identityToSecKeyRef(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 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
}
enterprise-certificate-proxy-0.2.0/internal/signer/darwin/keychain/keychain_test.go 0000664 0000000 0000000 00000001613 14315151263 0030675 0 ustar 00root root 0000000 0000000 //go:build darwin && cgo
// +build darwin,cgo
package keychain
import (
"bytes"
"crypto"
"testing"
"unsafe"
)
type signerOpts crypto.Hash
func (s signerOpts) HashFunc() crypto.Hash {
return crypto.Hash(s)
}
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)
}
}
enterprise-certificate-proxy-0.2.0/internal/signer/darwin/signer.go 0000664 0000000 0000000 00000005765 14315151263 0025553 0 ustar 00root root 0000000 0000000 // Copyright 2022 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// 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"
"signer/keychain"
"signer/util"
"time"
)
func init() {
gob.Register(crypto.SHA256)
gob.Register(crypto.SHA384)
gob.Register(crypto.SHA512)
gob.Register(&rsa.PSSOptions{})
}
// SignArgs contains arguments to a crypto Signer.Sign method.
type SignArgs struct {
Digest []byte // The content to sign.
Opts crypto.SignerOpts // Options for signing, such as Hash identifier.
}
// 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.
func (k *EnterpriseCertSigner) Sign(args SignArgs, resp *[]byte) (err error) {
*resp, err = k.key.Sign(nil, args.Digest, args.Opts)
return
}
func main() {
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)
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.2.0/internal/signer/darwin/util/ 0000775 0000000 0000000 00000000000 14315151263 0024675 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/internal/signer/darwin/util/test_data/ 0000775 0000000 0000000 00000000000 14315151263 0026645 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/internal/signer/darwin/util/test_data/certificate_config.json 0000664 0000000 0000000 00000000152 14315151263 0033345 0 ustar 00root root 0000000 0000000 {
"cert_configs": {
"macos_keychain": {
"issuer": "Google Endpoint Verification"
}
}
}
enterprise-certificate-proxy-0.2.0/internal/signer/darwin/util/util.go 0000664 0000000 0000000 00000001774 14315151263 0026212 0 ustar 00root root 0000000 0000000 // Package util provides helper functions for the signer.
package util
import (
"encoding/json"
"io/ioutil"
"os"
)
// EnterpriseCertificateConfig contains parameters for initializing signer.
type EnterpriseCertificateConfig struct {
CertConfigs CertConfigs `json:"cert_configs"`
}
// Container for various ECP Configs.
type CertConfigs struct {
MacOSKeychain MacOSKeychain `json:"macos_keychain"`
}
// MacOSKeychain contains parameters describing the certificate to use.
type MacOSKeychain struct {
Issuer string `json:"issuer"`
}
// 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 := ioutil.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.2.0/internal/signer/darwin/util/util_test.go 0000664 0000000 0000000 00000000606 14315151263 0027242 0 ustar 00root root 0000000 0000000 package util
import (
"testing"
)
func TestLoadConfig(t *testing.T) {
config, err := LoadConfig("./test_data/certificate_config.json")
if err != nil {
t.Errorf("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)
}
}
enterprise-certificate-proxy-0.2.0/internal/signer/linux/ 0000775 0000000 0000000 00000000000 14315151263 0023573 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/internal/signer/linux/go.mod 0000664 0000000 0000000 00000000103 14315151263 0024673 0 ustar 00root root 0000000 0000000 module signer
go 1.18
require github.com/google/go-pkcs11 v0.2.0
enterprise-certificate-proxy-0.2.0/internal/signer/linux/go.sum 0000664 0000000 0000000 00000000626 14315151263 0024732 0 ustar 00root root 0000000 0000000 github.com/google/go-pkcs11 v0.1.1-0.20220804004530-aced8594bb2e h1:y7UBq7yC0nK2b4h9uisyrhYVd21Ju/2GyzRve8dOvtk=
github.com/google/go-pkcs11 v0.1.1-0.20220804004530-aced8594bb2e/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY=
github.com/google/go-pkcs11 v0.2.0 h1:5meDPB26aJ98f+K9G21f0AqZwo/S5BJMJh8nuhMbdsI=
github.com/google/go-pkcs11 v0.2.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY=
enterprise-certificate-proxy-0.2.0/internal/signer/linux/signer.go 0000664 0000000 0000000 00000006046 14315151263 0025417 0 ustar 00root root 0000000 0000000 // Copyright 2022 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// 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"
"signer/util"
"time"
)
func init() {
gob.Register(crypto.SHA256)
gob.Register(crypto.SHA384)
gob.Register(crypto.SHA512)
gob.Register(&rsa.PSSOptions{})
}
// SignArgs contains arguments to a crypto Signer.Sign method.
type SignArgs struct {
Digest []byte // The content to sign.
Opts crypto.SignerOpts // Options for signing, such as Hash identifier.
}
// A EnterpriseCertSigner exports RPC methods for signing.
type EnterpriseCertSigner struct {
key *util.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.
func (k *EnterpriseCertSigner) Sign(args SignArgs, resp *[]byte) (err error) {
*resp, err = k.key.Sign(nil, args.Digest, args.Opts)
return
}
func main() {
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)
enterpriseCertSigner := new(EnterpriseCertSigner)
enterpriseCertSigner.key, err = util.Cred(config.CertConfigs.PKCS11.PKCS11Module, config.CertConfigs.PKCS11.Slot, config.CertConfigs.PKCS11.Label)
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.2.0/internal/signer/linux/util/ 0000775 0000000 0000000 00000000000 14315151263 0024550 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/internal/signer/linux/util/cert_util.go 0000664 0000000 0000000 00000004333 14315151263 0027074 0 ustar 00root root 0000000 0000000 // Cert_util provides helpers for working with certificates via PKCS11
package util
import (
"crypto"
"errors"
"io"
"github.com/google/go-pkcs11/pkcs11"
)
// 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) (*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{})
certs, err := kslot.Objects(pkcs11.Filter{Class: pkcs11.ClassCertificate, Label: label})
if err != nil {
return nil, err
}
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
}
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
}
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")
}
return &Key{
slot: kslot,
signer: ksigner,
chain: kchain,
}, 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
}
// 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()
}
// 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)
}
enterprise-certificate-proxy-0.2.0/internal/signer/linux/util/test_data/ 0000775 0000000 0000000 00000000000 14315151263 0026520 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/internal/signer/linux/util/test_data/certificate_config.json 0000664 0000000 0000000 00000000207 14315151263 0033221 0 ustar 00root root 0000000 0000000 {
"cert_configs": {
"pkcs11": {
"slot": "0x1739427",
"label": "gecc",
"module": "pkcs11_module.so"
}
}
}
enterprise-certificate-proxy-0.2.0/internal/signer/linux/util/util.go 0000664 0000000 0000000 00000002765 14315151263 0026066 0 ustar 00root root 0000000 0000000 // Package util provides helper functions for the signer.
package util
import (
"encoding/json"
"io/ioutil"
"os"
"strconv"
"strings"
)
// 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
}
// EnterpriseCertificateConfig contains parameters for initializing signer.
type EnterpriseCertificateConfig struct {
CertConfigs CertConfigs `json:"cert_configs"`
}
// Container for various ECP Configs.
type CertConfigs struct {
PKCS11 PKCS11 `json:"pkcs11"`
}
// PKCS11 contains 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)
}
// 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 := ioutil.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.2.0/internal/signer/linux/util/util_test.go 0000664 0000000 0000000 00000002346 14315151263 0027120 0 ustar 00root root 0000000 0000000 package util
import (
"testing"
)
func TestLoadConfig(t *testing.T) {
config, err := LoadConfig("./test_data/certificate_config.json")
if err != nil {
t.Fatalf("LoadConfig error: %v", err)
}
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)
}
}
func TestLoadConfigMissing(t *testing.T) {
_, err := LoadConfig("./test_data/certificate_config_missing.json")
if err == nil {
t.Error("Expected error but got nil")
}
}
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")
}
}
enterprise-certificate-proxy-0.2.0/internal/signer/test/ 0000775 0000000 0000000 00000000000 14315151263 0023413 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/internal/signer/test/signer.go 0000664 0000000 0000000 00000004660 14315151263 0025237 0 ustar 00root root 0000000 0000000 // Copyright 2022 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// 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"
"io/ioutil"
"log"
"net/rpc"
"os"
"time"
)
// SignArgs encapsulate the parameters for the Sign method.
type SignArgs struct {
Digest []byte
Opts crypto.SignerOpts
}
// 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.
func (k *EnterpriseCertSigner) Sign(args SignArgs, resp *[]byte) (err error) {
*resp = args.Digest
return nil
}
func main() {
enterpriseCertSigner := new(EnterpriseCertSigner)
data, err := ioutil.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.2.0/internal/signer/windows/ 0000775 0000000 0000000 00000000000 14315151263 0024126 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/internal/signer/windows/go.mod 0000664 0000000 0000000 00000000251 14315151263 0025232 0 ustar 00root root 0000000 0000000 module signer
go 1.18
require (
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
)
enterprise-certificate-proxy-0.2.0/internal/signer/windows/go.sum 0000664 0000000 0000000 00000002171 14315151263 0025262 0 ustar 00root root 0000000 0000000 golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
enterprise-certificate-proxy-0.2.0/internal/signer/windows/ncrypt/ 0000775 0000000 0000000 00000000000 14315151263 0025445 5 ustar 00root root 0000000 0000000 enterprise-certificate-proxy-0.2.0/internal/signer/windows/ncrypt/cert_util.go 0000664 0000000 0000000 00000025136 14315151263 0027775 0 ustar 00root root 0000000 0000000 //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<