pax_global_header00006660000000000000000000000064143151512630014513gustar00rootroot0000000000000052 comment=c5f65f94cd903cc16bba58964863229eb35ec41c enterprise-certificate-proxy-0.2.0/000077500000000000000000000000001431515126300173315ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/.github/000077500000000000000000000000001431515126300206715ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/.github/workflows/000077500000000000000000000000001431515126300227265ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/.github/workflows/test-client.yml000066400000000000000000000006061431515126300257060ustar00rootroot00000000000000name: 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.yml000066400000000000000000000006631431515126300260440ustar00rootroot00000000000000name: 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.yml000066400000000000000000000007421431515126300272020ustar00rootroot00000000000000name: 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.yml000066400000000000000000000007401431515126300270530ustar00rootroot00000000000000name: 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.yml000066400000000000000000000007471431515126300274150ustar00rootroot00000000000000name: 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/.gitignore000066400000000000000000000000641431515126300213210ustar00rootroot00000000000000# MacOS .DS_Store # compiled binaries build/bin/** enterprise-certificate-proxy-0.2.0/CODEOWNERS000066400000000000000000000001051431515126300207200ustar00rootroot00000000000000# Default owner for all directories not owned by others * @andyrzhao enterprise-certificate-proxy-0.2.0/CODE_OF_CONDUCT.md000066400000000000000000000106271431515126300221360ustar00rootroot00000000000000# 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.md000066400000000000000000000021111431515126300215550ustar00rootroot00000000000000# 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/LICENSE000066400000000000000000000261361431515126300203460ustar00rootroot00000000000000 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.md000066400000000000000000000117161431515126300206160ustar00rootroot00000000000000# 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.md000066400000000000000000000005111431515126300211170ustar00rootroot00000000000000# 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/000077500000000000000000000000001431515126300204305ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/build/scripts/000077500000000000000000000000001431515126300221175ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/build/scripts/darwin_amd64.sh000077500000000000000000000006201431515126300247330ustar00rootroot00000000000000#!/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.sh000077500000000000000000000005761431515126300246200ustar00rootroot00000000000000#!/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.ps1000066400000000000000000000011261431515126300252310ustar00rootroot00000000000000$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/000077500000000000000000000000001431515126300206075ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/client/client.go000066400000000000000000000123531431515126300224200ustar00rootroot00000000000000// 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.go000066400000000000000000000040351431515126300234550ustar00rootroot00000000000000// 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/000077500000000000000000000000001431515126300224205ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/client/testdata/certificate_config.json000066400000000000000000000002011431515126300271130ustar00rootroot00000000000000{ "cert_configs": { "test": { "issuer": "Test Issuer" } }, "libs": { "ecp": "./testdata/signer.sh" } } enterprise-certificate-proxy-0.2.0/client/testdata/signer.sh000077500000000000000000000003211431515126300242420ustar00rootroot00000000000000#!/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.pementerprise-certificate-proxy-0.2.0/client/testdata/testcert.pem000066400000000000000000000056011431515126300247620ustar00rootroot00000000000000-----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/000077500000000000000000000000001431515126300215645ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/client/util/test_data/000077500000000000000000000000001431515126300235345ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/client/util/test_data/certificate_config.json000066400000000000000000000001411431515126300302320ustar00rootroot00000000000000{ "libs": { "ecp": "C:/Program Files (x86)/Google/Endpoint Verification/signer.exe" } } enterprise-certificate-proxy-0.2.0/client/util/util.go000066400000000000000000000033651431515126300230770ustar00rootroot00000000000000// 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.go000066400000000000000000000005741431515126300241350ustar00rootroot00000000000000package 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/000077500000000000000000000000001431515126300207425ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/cshared/main.go000066400000000000000000000071231431515126300222200ustar00rootroot00000000000000// 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.mod000066400000000000000000000001031431515126300204310ustar00rootroot00000000000000module github.com/googleapis/enterprise-certificate-proxy go 1.18 enterprise-certificate-proxy-0.2.0/go.sum000066400000000000000000000000001431515126300204520ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/internal/000077500000000000000000000000001431515126300211455ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/internal/signer/000077500000000000000000000000001431515126300224345ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/internal/signer/darwin/000077500000000000000000000000001431515126300237205ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/internal/signer/darwin/go.mod000066400000000000000000000000261431515126300250240ustar00rootroot00000000000000module signer go 1.18enterprise-certificate-proxy-0.2.0/internal/signer/darwin/keychain/000077500000000000000000000000001431515126300255135ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/internal/signer/darwin/keychain/keychain.go000066400000000000000000000317701431515126300276450ustar00rootroot00000000000000//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.go000066400000000000000000000016131431515126300306750ustar00rootroot00000000000000//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.go000066400000000000000000000057651431515126300255530ustar00rootroot00000000000000// 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/000077500000000000000000000000001431515126300246755ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/internal/signer/darwin/util/test_data/000077500000000000000000000000001431515126300266455ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/internal/signer/darwin/util/test_data/certificate_config.json000066400000000000000000000001521431515126300333450ustar00rootroot00000000000000{ "cert_configs": { "macos_keychain": { "issuer": "Google Endpoint Verification" } } } enterprise-certificate-proxy-0.2.0/internal/signer/darwin/util/util.go000066400000000000000000000017741431515126300262120ustar00rootroot00000000000000// 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.go000066400000000000000000000006061431515126300272420ustar00rootroot00000000000000package 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/000077500000000000000000000000001431515126300235735ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/internal/signer/linux/go.mod000066400000000000000000000001031431515126300246730ustar00rootroot00000000000000module signer go 1.18 require github.com/google/go-pkcs11 v0.2.0 enterprise-certificate-proxy-0.2.0/internal/signer/linux/go.sum000066400000000000000000000006261431515126300247320ustar00rootroot00000000000000github.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.go000066400000000000000000000060461431515126300254170ustar00rootroot00000000000000// 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/000077500000000000000000000000001431515126300245505ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/internal/signer/linux/util/cert_util.go000066400000000000000000000043331431515126300270740ustar00rootroot00000000000000// 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/000077500000000000000000000000001431515126300265205ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/internal/signer/linux/util/test_data/certificate_config.json000066400000000000000000000002071431515126300332210ustar00rootroot00000000000000{ "cert_configs": { "pkcs11": { "slot": "0x1739427", "label": "gecc", "module": "pkcs11_module.so" } } } enterprise-certificate-proxy-0.2.0/internal/signer/linux/util/util.go000066400000000000000000000027651431515126300260660ustar00rootroot00000000000000// 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.go000066400000000000000000000023461431515126300271200ustar00rootroot00000000000000package 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/000077500000000000000000000000001431515126300234135ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/internal/signer/test/signer.go000066400000000000000000000046601431515126300252370ustar00rootroot00000000000000// 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/000077500000000000000000000000001431515126300241265ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/internal/signer/windows/go.mod000066400000000000000000000002511431515126300252320ustar00rootroot00000000000000module 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.sum000066400000000000000000000021711431515126300252620ustar00rootroot00000000000000golang.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/000077500000000000000000000000001431515126300254455ustar00rootroot00000000000000enterprise-certificate-proxy-0.2.0/internal/signer/windows/ncrypt/cert_util.go000066400000000000000000000251361431515126300277750ustar00rootroot00000000000000//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<