Vendor in c/image with sigstore support

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
This commit is contained in:
Miloslav Trmač
2022-07-06 07:15:31 +02:00
parent b95e081162
commit 06be7a1559
424 changed files with 35364 additions and 4746 deletions

14
vendor/github.com/sigstore/sigstore/COPYRIGHT.txt generated vendored Normal file
View File

@@ -0,0 +1,14 @@
Copyright 2021 The Sigstore Authors.
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.

202
vendor/github.com/sigstore/sigstore/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,202 @@
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.

View File

@@ -0,0 +1,170 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package cryptoutils
import (
"bytes"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"
"math/big"
"time"
)
const (
// CertificatePEMType is the string "CERTIFICATE" to be used during PEM encoding and decoding
CertificatePEMType PEMType = "CERTIFICATE"
)
// MarshalCertificateToPEM converts the provided X509 certificate into PEM format
func MarshalCertificateToPEM(cert *x509.Certificate) ([]byte, error) {
if cert == nil {
return nil, errors.New("nil certificate provided")
}
return PEMEncode(CertificatePEMType, cert.Raw), nil
}
// MarshalCertificatesToPEM converts the provided X509 certificates into PEM format
func MarshalCertificatesToPEM(certs []*x509.Certificate) ([]byte, error) {
buf := bytes.Buffer{}
for _, cert := range certs {
pemBytes, err := MarshalCertificateToPEM(cert)
if err != nil {
return nil, err
}
_, _ = buf.Write(pemBytes)
}
return buf.Bytes(), nil
}
// UnmarshalCertificatesFromPEM extracts one or more X509 certificates from the provided
// byte slice, which is assumed to be in PEM-encoded format.
func UnmarshalCertificatesFromPEM(pemBytes []byte) ([]*x509.Certificate, error) {
result := []*x509.Certificate{}
remaining := pemBytes
for len(remaining) > 0 {
var certDer *pem.Block
certDer, remaining = pem.Decode(remaining)
if certDer == nil {
return nil, errors.New("error during PEM decoding")
}
cert, err := x509.ParseCertificate(certDer.Bytes)
if err != nil {
return nil, err
}
result = append(result, cert)
}
return result, nil
}
// UnmarshalCertificatesFromPEMLimited extracts one or more X509 certificates from the provided
// byte slice, which is assumed to be in PEM-encoded format. Fails after a specified
// number of iterations. A reasonable limit is 10 iterations.
func UnmarshalCertificatesFromPEMLimited(pemBytes []byte, iterations int) ([]*x509.Certificate, error) {
result := []*x509.Certificate{}
remaining := pemBytes
count := 0
for len(remaining) > 0 {
if count == iterations {
return nil, errors.New("too many certificates specified in PEM block")
}
var certDer *pem.Block
certDer, remaining = pem.Decode(remaining)
if certDer == nil {
return nil, errors.New("error during PEM decoding")
}
cert, err := x509.ParseCertificate(certDer.Bytes)
if err != nil {
return nil, err
}
result = append(result, cert)
count++
}
return result, nil
}
// LoadCertificatesFromPEM extracts one or more X509 certificates from the provided
// io.Reader.
func LoadCertificatesFromPEM(pem io.Reader) ([]*x509.Certificate, error) {
fileBytes, err := io.ReadAll(pem)
if err != nil {
return nil, err
}
return UnmarshalCertificatesFromPEM(fileBytes)
}
func formatTime(t time.Time) string {
return t.UTC().Format(time.RFC3339)
}
// CheckExpiration verifies that epoch is during the validity period of
// the certificate provided.
//
// It returns nil if issueTime < epoch < expirationTime, and error otherwise.
func CheckExpiration(cert *x509.Certificate, epoch time.Time) error {
if cert == nil {
return errors.New("certificate is nil")
}
if cert.NotAfter.Before(epoch) {
return fmt.Errorf("certificate expiration time %s is before %s", formatTime(cert.NotAfter), formatTime(epoch))
}
if cert.NotBefore.After(epoch) {
return fmt.Errorf("certificate issued time %s is before %s", formatTime(cert.NotBefore), formatTime(epoch))
}
return nil
}
// ParseCSR parses a PKCS#10 PEM-encoded CSR.
func ParseCSR(csr []byte) (*x509.CertificateRequest, error) {
derBlock, _ := pem.Decode(csr)
if derBlock == nil || derBlock.Bytes == nil {
return nil, errors.New("no CSR found while decoding")
}
correctType := false
acceptedHeaders := []string{"CERTIFICATE REQUEST", "NEW CERTIFICATE REQUEST"}
for _, v := range acceptedHeaders {
if derBlock.Type == v {
correctType = true
}
}
if !correctType {
return nil, fmt.Errorf("DER type %v is not of any type %v for CSR", derBlock.Type, acceptedHeaders)
}
return x509.ParseCertificateRequest(derBlock.Bytes)
}
// GenerateSerialNumber creates a compliant serial number as per RFC 5280 4.1.2.2.
// Serial numbers must be positive, and can be no longer than 20 bytes.
// The serial number is generated with 159 bits, so that the first bit will always
// be 0, resulting in a positive serial number.
func GenerateSerialNumber() (*big.Int, error) {
// Pick a random number from 0 to 2^159.
serial, err := rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil))
if err != nil {
return nil, errors.New("error generating serial number")
}
return serial, nil
}

View File

@@ -0,0 +1,31 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package cryptoutils
import (
"encoding/pem"
)
// PEMType is a specific type for string constants used during PEM encoding and decoding
type PEMType string
// PEMEncode encodes the specified byte slice in PEM format using the provided type string
func PEMEncode(typeStr PEMType, bytes []byte) []byte {
return pem.EncodeToMemory(&pem.Block{
Type: string(typeStr),
Bytes: bytes,
})
}

View File

@@ -0,0 +1,96 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package cryptoutils
import (
"errors"
"fmt"
"io/ioutil"
"os"
"golang.org/x/term"
)
// PassFunc is a type of function that takes a boolean (representing whether confirmation is desired) and returns the password as read, along with an error if one occurred
type PassFunc func(bool) ([]byte, error)
var (
// Read is for fuzzing
Read = readPasswordFn
)
// readPasswordFn reads the password from the following sources, in order of preference:
//
// - COSIGN_PASSWORD environment variable
//
// - user input from from terminal (if present)
//
// - provided to stdin from pipe
func readPasswordFn() func() ([]byte, error) {
if pw, ok := os.LookupEnv("COSIGN_PASSWORD"); ok {
return func() ([]byte, error) {
return []byte(pw), nil
}
}
if term.IsTerminal(0) {
return func() ([]byte, error) {
return term.ReadPassword(0)
}
}
// Handle piped in passwords.
return func() ([]byte, error) {
return ioutil.ReadAll(os.Stdin)
}
}
// StaticPasswordFunc returns a PassFunc which returns the provided password.
func StaticPasswordFunc(pw []byte) PassFunc {
return func(bool) ([]byte, error) {
return pw, nil
}
}
// SkipPassword is a PassFunc that does not interact with a user, but
// simply returns nil for both the password result and error struct.
func SkipPassword(_ bool) ([]byte, error) {
return nil, nil
}
// GetPasswordFromStdIn gathers the password from stdin with an
// optional confirmation step.
func GetPasswordFromStdIn(confirm bool) ([]byte, error) {
read := Read()
fmt.Fprint(os.Stderr, "Enter password for private key: ")
pw1, err := read()
fmt.Fprintln(os.Stderr)
if err != nil {
return nil, err
}
if !confirm {
return pw1, nil
}
fmt.Fprint(os.Stderr, "Enter again: ")
pw2, err := read()
fmt.Fprintln(os.Stderr)
if err != nil {
return nil, err
}
if string(pw1) != string(pw2) {
return nil, errors.New("passwords do not match")
}
return pw1, nil
}

View File

@@ -0,0 +1,144 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package cryptoutils
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"github.com/theupdateframework/go-tuf/encrypted"
)
const (
// PrivateKeyPEMType is the string "PRIVATE KEY" to be used during PEM encoding and decoding
PrivateKeyPEMType PEMType = "PRIVATE KEY"
encryptedCosignPrivateKeyPEMType PEMType = "ENCRYPTED COSIGN PRIVATE KEY"
// EncryptedSigstorePrivateKeyPEMType is the string "ENCRYPTED SIGSTORE PRIVATE KEY" to be used during PEM encoding and decoding
EncryptedSigstorePrivateKeyPEMType PEMType = "ENCRYPTED SIGSTORE PRIVATE KEY"
)
func pemEncodeKeyPair(priv crypto.PrivateKey, pub crypto.PublicKey, pf PassFunc) (privPEM, pubPEM []byte, err error) {
pubPEM, err = MarshalPublicKeyToPEM(pub)
if err != nil {
return nil, nil, err
}
derBytes, err := MarshalPrivateKeyToDER(priv)
if err != nil {
return nil, nil, err
}
if pf == nil {
return PEMEncode(PrivateKeyPEMType, derBytes), pubPEM, nil
}
password, err := pf(true)
if err != nil {
return nil, nil, err
}
if password == nil {
return PEMEncode(PrivateKeyPEMType, derBytes), pubPEM, nil
}
if derBytes, err = encrypted.Encrypt(derBytes, password); err != nil {
return nil, nil, err
}
return PEMEncode(EncryptedSigstorePrivateKeyPEMType, derBytes), pubPEM, nil
}
// GeneratePEMEncodedECDSAKeyPair generates an ECDSA keypair, optionally password encrypted using a provided PassFunc, and PEM encoded.
func GeneratePEMEncodedECDSAKeyPair(curve elliptic.Curve, pf PassFunc) (privPEM, pubPEM []byte, err error) {
priv, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, nil, err
}
return pemEncodeKeyPair(priv, priv.Public(), pf)
}
// GeneratePEMEncodedRSAKeyPair generates an RSA keypair, optionally password encrypted using a provided PassFunc, and PEM encoded.
func GeneratePEMEncodedRSAKeyPair(keyLengthBits int, pf PassFunc) (privPEM, pubPEM []byte, err error) {
priv, err := rsa.GenerateKey(rand.Reader, keyLengthBits)
if err != nil {
return nil, nil, err
}
return pemEncodeKeyPair(priv, priv.Public(), pf)
}
// MarshalPrivateKeyToEncryptedDER marshals the private key and encrypts the DER-encoded value using the specified password function
func MarshalPrivateKeyToEncryptedDER(priv crypto.PrivateKey, pf PassFunc) ([]byte, error) {
derKey, err := MarshalPrivateKeyToDER(priv)
if err != nil {
return nil, err
}
password, err := pf(true)
if err != nil {
return nil, err
}
if password == nil {
return nil, errors.New("password was nil")
}
return encrypted.Encrypt(derKey, password)
}
// UnmarshalPEMToPrivateKey converts a PEM-encoded byte slice into a crypto.PrivateKey
func UnmarshalPEMToPrivateKey(pemBytes []byte, pf PassFunc) (crypto.PrivateKey, error) {
derBlock, _ := pem.Decode(pemBytes)
if derBlock == nil {
return nil, errors.New("PEM decoding failed")
}
switch derBlock.Type {
case string(PrivateKeyPEMType):
return x509.ParsePKCS8PrivateKey(derBlock.Bytes)
case string(EncryptedSigstorePrivateKeyPEMType), string(encryptedCosignPrivateKeyPEMType):
derBytes := derBlock.Bytes
if pf != nil {
password, err := pf(false)
if err != nil {
return nil, err
}
if password != nil {
derBytes, err = encrypted.Decrypt(derBytes, password)
if err != nil {
return nil, err
}
}
}
return x509.ParsePKCS8PrivateKey(derBytes)
}
return nil, fmt.Errorf("unknown PEM file type: %v", derBlock.Type)
}
// MarshalPrivateKeyToDER converts a crypto.PrivateKey into a PKCS8 ASN.1 DER byte slice
func MarshalPrivateKeyToDER(priv crypto.PrivateKey) ([]byte, error) {
if priv == nil {
return nil, errors.New("empty key")
}
return x509.MarshalPKCS8PrivateKey(priv)
}
// MarshalPrivateKeyToPEM converts a crypto.PrivateKey into a PEM-encoded byte slice
func MarshalPrivateKeyToPEM(priv crypto.PrivateKey) ([]byte, error) {
derBytes, err := MarshalPrivateKeyToDER(priv)
if err != nil {
return nil, err
}
return PEMEncode(PrivateKeyPEMType, derBytes), nil
}

View File

@@ -0,0 +1,174 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package cryptoutils
import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rsa"
"crypto/sha1" // nolint:gosec
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"github.com/letsencrypt/boulder/goodkey"
)
const (
// PublicKeyPEMType is the string "PUBLIC KEY" to be used during PEM encoding and decoding
PublicKeyPEMType PEMType = "PUBLIC KEY"
)
// subjectPublicKeyInfo is used to construct a subject key ID.
// https://tools.ietf.org/html/rfc5280#section-4.1.2.7
type subjectPublicKeyInfo struct {
Algorithm pkix.AlgorithmIdentifier
SubjectPublicKey asn1.BitString
}
// UnmarshalPEMToPublicKey converts a PEM-encoded byte slice into a crypto.PublicKey
func UnmarshalPEMToPublicKey(pemBytes []byte) (crypto.PublicKey, error) {
derBytes, _ := pem.Decode(pemBytes)
if derBytes == nil {
return nil, errors.New("PEM decoding failed")
}
return x509.ParsePKIXPublicKey(derBytes.Bytes)
}
// MarshalPublicKeyToDER converts a crypto.PublicKey into a PKIX, ASN.1 DER byte slice
func MarshalPublicKeyToDER(pub crypto.PublicKey) ([]byte, error) {
if pub == nil {
return nil, errors.New("empty key")
}
return x509.MarshalPKIXPublicKey(pub)
}
// MarshalPublicKeyToPEM converts a crypto.PublicKey into a PEM-encoded byte slice
func MarshalPublicKeyToPEM(pub crypto.PublicKey) ([]byte, error) {
derBytes, err := MarshalPublicKeyToDER(pub)
if err != nil {
return nil, err
}
return PEMEncode(PublicKeyPEMType, derBytes), nil
}
// SKID generates a 160-bit SHA-1 hash of the value of the BIT STRING
// subjectPublicKey (excluding the tag, length, and number of unused bits).
// https://tools.ietf.org/html/rfc5280#section-4.2.1.2
func SKID(pub crypto.PublicKey) ([]byte, error) {
derPubBytes, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
return nil, err
}
var spki subjectPublicKeyInfo
if _, err := asn1.Unmarshal(derPubBytes, &spki); err != nil {
return nil, err
}
skid := sha1.Sum(spki.SubjectPublicKey.Bytes) // nolint:gosec
return skid[:], nil
}
// EqualKeys compares two public keys. Supports RSA, ECDSA and ED25519.
// If not equal, the error message contains hex-encoded SHA1 hashes of the DER-encoded keys
func EqualKeys(first, second crypto.PublicKey) error {
switch pub := first.(type) {
case *rsa.PublicKey:
if !pub.Equal(second) {
return fmt.Errorf(genErrMsg(first, second, "rsa"))
}
case *ecdsa.PublicKey:
if !pub.Equal(second) {
return fmt.Errorf(genErrMsg(first, second, "ecdsa"))
}
case ed25519.PublicKey:
if !pub.Equal(second) {
return fmt.Errorf(genErrMsg(first, second, "ed25519"))
}
default:
return errors.New("unsupported key type")
}
return nil
}
// genErrMsg generates an error message for EqualKeys
func genErrMsg(first, second crypto.PublicKey, keyType string) string {
msg := fmt.Sprintf("%s public keys are not equal", keyType)
// Calculate SKID to include in error message
firstSKID, err := SKID(first)
if err != nil {
return msg
}
secondSKID, err := SKID(second)
if err != nil {
return msg
}
return fmt.Sprintf("%s (%s, %s)", msg, hex.EncodeToString(firstSKID), hex.EncodeToString(secondSKID))
}
// ValidatePubKey validates the parameters of an RSA, ECDSA, or ED25519 public key.
func ValidatePubKey(pub crypto.PublicKey) error {
switch pk := pub.(type) {
case *rsa.PublicKey:
// goodkey policy enforces:
// * Size of key: 2048 <= size <= 4096, size % 8 = 0
// * Exponent E = 65537 (Default exponent for OpenSSL and Golang)
// * Small primes check for modulus
// * Weak keys generated by Infineon hardware (see https://crocs.fi.muni.cz/public/papers/rsa_ccs17)
// * Key is easily factored with Fermat's factorization method
p, err := goodkey.NewKeyPolicy(&goodkey.Config{FermatRounds: 100}, nil)
if err != nil {
// Should not occur, only chances to return errors are if fermat rounds
// are <0 or when loading blocked/weak keys from disk (not used here)
return errors.New("unable to initialize key policy")
}
// ctx is unused
return p.GoodKey(context.Background(), pub)
case *ecdsa.PublicKey:
// Unable to use goodkey policy because P-521 curve is not supported
return validateEcdsaKey(pk)
case ed25519.PublicKey:
return validateEd25519Key(pk)
}
return errors.New("unsupported public key type")
}
// Enforce that the ECDSA key curve is one of:
// * NIST P-256 (secp256r1, prime256v1)
// * NIST P-384
// * NIST P-521.
// Other EC curves, like secp256k1, are not supported by Go.
func validateEcdsaKey(pub *ecdsa.PublicKey) error {
switch pub.Curve {
case elliptic.P224():
return fmt.Errorf("unsupported ec curve, expected NIST P-256, P-384, or P-521")
case elliptic.P256(), elliptic.P384(), elliptic.P521():
return nil
default:
return fmt.Errorf("unexpected ec curve")
}
}
// No validations currently, ED25519 supports only one key size.
func validateEd25519Key(pub ed25519.PublicKey) error {
return nil
}

View File

@@ -0,0 +1,17 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
// Package signature contains types and utilities related to Sigstore signatures.
package signature

View File

@@ -0,0 +1,244 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package signature
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"errors"
"fmt"
"io"
"github.com/sigstore/sigstore/pkg/signature/options"
)
// checked on LoadSigner, LoadVerifier and SignMessage
var ecdsaSupportedHashFuncs = []crypto.Hash{
crypto.SHA256,
crypto.SHA512,
crypto.SHA384,
crypto.SHA224,
}
// checked on VerifySignature. Supports SHA1 verification.
var ecdsaSupportedVerifyHashFuncs = []crypto.Hash{
crypto.SHA256,
crypto.SHA512,
crypto.SHA384,
crypto.SHA224,
crypto.SHA1,
}
// ECDSASigner is a signature.Signer that uses an Elliptic Curve DSA algorithm
type ECDSASigner struct {
hashFunc crypto.Hash
priv *ecdsa.PrivateKey
}
// LoadECDSASigner calculates signatures using the specified private key and hash algorithm.
//
// hf must not be crypto.Hash(0).
func LoadECDSASigner(priv *ecdsa.PrivateKey, hf crypto.Hash) (*ECDSASigner, error) {
if priv == nil {
return nil, errors.New("invalid ECDSA private key specified")
}
if !isSupportedAlg(hf, ecdsaSupportedHashFuncs) {
return nil, errors.New("invalid hash function specified")
}
return &ECDSASigner{
priv: priv,
hashFunc: hf,
}, nil
}
// SignMessage signs the provided message. If the message is provided,
// this method will compute the digest according to the hash function specified
// when the ECDSASigner was created.
//
// This function recognizes the following Options listed in order of preference:
//
// - WithRand()
//
// - WithDigest()
//
// - WithCryptoSignerOpts()
//
// All other options are ignored if specified.
func (e ECDSASigner) SignMessage(message io.Reader, opts ...SignOption) ([]byte, error) {
digest, _, err := ComputeDigestForSigning(message, e.hashFunc, ecdsaSupportedHashFuncs, opts...)
if err != nil {
return nil, err
}
rand := selectRandFromOpts(opts...)
return ecdsa.SignASN1(rand, e.priv, digest)
}
// Public returns the public key that can be used to verify signatures created by
// this signer.
func (e ECDSASigner) Public() crypto.PublicKey {
if e.priv == nil {
return nil
}
return e.priv.Public()
}
// PublicKey returns the public key that can be used to verify signatures created by
// this signer. As this value is held in memory, all options provided in arguments
// to this method are ignored.
func (e ECDSASigner) PublicKey(_ ...PublicKeyOption) (crypto.PublicKey, error) {
return e.Public(), nil
}
// Sign computes the signature for the specified digest. If a source of entropy is
// given in rand, it will be used instead of the default value (rand.Reader from crypto/rand).
//
// If opts are specified, the hash function in opts.Hash should be the one used to compute
// digest. If opts are not specified, the value provided when the signer was created will be used instead.
func (e ECDSASigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
ecdsaOpts := []SignOption{options.WithDigest(digest), options.WithRand(rand)}
if opts != nil {
ecdsaOpts = append(ecdsaOpts, options.WithCryptoSignerOpts(opts))
}
return e.SignMessage(nil, ecdsaOpts...)
}
// ECDSAVerifier is a signature.Verifier that uses an Elliptic Curve DSA algorithm
type ECDSAVerifier struct {
publicKey *ecdsa.PublicKey
hashFunc crypto.Hash
}
// LoadECDSAVerifier returns a Verifier that verifies signatures using the specified
// ECDSA public key and hash algorithm.
//
// hf must not be crypto.Hash(0).
func LoadECDSAVerifier(pub *ecdsa.PublicKey, hashFunc crypto.Hash) (*ECDSAVerifier, error) {
if pub == nil {
return nil, errors.New("invalid ECDSA public key specified")
}
if !isSupportedAlg(hashFunc, ecdsaSupportedHashFuncs) {
return nil, errors.New("invalid hash function specified")
}
return &ECDSAVerifier{
publicKey: pub,
hashFunc: hashFunc,
}, nil
}
// PublicKey returns the public key that is used to verify signatures by
// this verifier. As this value is held in memory, all options provided in arguments
// to this method are ignored.
func (e ECDSAVerifier) PublicKey(_ ...PublicKeyOption) (crypto.PublicKey, error) {
return e.publicKey, nil
}
// VerifySignature verifies the signature for the given message. Unless provided
// in an option, the digest of the message will be computed using the hash function specified
// when the ECDSAVerifier was created.
//
// This function returns nil if the verification succeeded, and an error message otherwise.
//
// This function recognizes the following Options listed in order of preference:
//
// - WithDigest()
//
// All other options are ignored if specified.
func (e ECDSAVerifier) VerifySignature(signature, message io.Reader, opts ...VerifyOption) error {
digest, _, err := ComputeDigestForVerifying(message, e.hashFunc, ecdsaSupportedVerifyHashFuncs, opts...)
if err != nil {
return err
}
if signature == nil {
return errors.New("nil signature passed to VerifySignature")
}
sigBytes, err := io.ReadAll(signature)
if err != nil {
return fmt.Errorf("reading signature: %w", err)
}
if !ecdsa.VerifyASN1(e.publicKey, digest, sigBytes) {
return errors.New("invalid signature when validating ASN.1 encoded signature")
}
return nil
}
// ECDSASignerVerifier is a signature.SignerVerifier that uses an Elliptic Curve DSA algorithm
type ECDSASignerVerifier struct {
*ECDSASigner
*ECDSAVerifier
}
// LoadECDSASignerVerifier creates a combined signer and verifier. This is a convenience object
// that simply wraps an instance of ECDSASigner and ECDSAVerifier.
func LoadECDSASignerVerifier(priv *ecdsa.PrivateKey, hf crypto.Hash) (*ECDSASignerVerifier, error) {
signer, err := LoadECDSASigner(priv, hf)
if err != nil {
return nil, fmt.Errorf("initializing signer: %w", err)
}
verifier, err := LoadECDSAVerifier(&priv.PublicKey, hf)
if err != nil {
return nil, fmt.Errorf("initializing verifier: %w", err)
}
return &ECDSASignerVerifier{
ECDSASigner: signer,
ECDSAVerifier: verifier,
}, nil
}
// NewDefaultECDSASignerVerifier creates a combined signer and verifier using ECDSA.
//
// This creates a new ECDSA key using the P-256 curve and uses the SHA256 hashing algorithm.
func NewDefaultECDSASignerVerifier() (*ECDSASignerVerifier, *ecdsa.PrivateKey, error) {
return NewECDSASignerVerifier(elliptic.P256(), rand.Reader, crypto.SHA256)
}
// NewECDSASignerVerifier creates a combined signer and verifier using ECDSA.
//
// This creates a new ECDSA key using the specified elliptic curve, entropy source, and hashing function.
func NewECDSASignerVerifier(curve elliptic.Curve, rand io.Reader, hashFunc crypto.Hash) (*ECDSASignerVerifier, *ecdsa.PrivateKey, error) {
priv, err := ecdsa.GenerateKey(curve, rand)
if err != nil {
return nil, nil, err
}
sv, err := LoadECDSASignerVerifier(priv, hashFunc)
if err != nil {
return nil, nil, err
}
return sv, priv, nil
}
// PublicKey returns the public key that is used to verify signatures by
// this verifier. As this value is held in memory, all options provided in arguments
// to this method are ignored.
func (e ECDSASignerVerifier) PublicKey(_ ...PublicKeyOption) (crypto.PublicKey, error) {
return e.publicKey, nil
}

View File

@@ -0,0 +1,197 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package signature
import (
"bytes"
"crypto"
"crypto/ed25519"
"crypto/rand"
"errors"
"fmt"
"io"
)
var ed25519SupportedHashFuncs = []crypto.Hash{
crypto.Hash(0),
}
// ED25519Signer is a signature.Signer that uses the Ed25519 public-key signature system
type ED25519Signer struct {
priv ed25519.PrivateKey
}
// LoadED25519Signer calculates signatures using the specified private key.
func LoadED25519Signer(priv ed25519.PrivateKey) (*ED25519Signer, error) {
if priv == nil {
return nil, errors.New("invalid ED25519 private key specified")
}
// check this to avoid panic and throw error gracefully
if len(priv) != ed25519.PrivateKeySize {
return nil, errors.New("invalid size for ED25519 key")
}
return &ED25519Signer{
priv: priv,
}, nil
}
// SignMessage signs the provided message. Passing the WithDigest option is not
// supported as ED25519 performs a two pass hash over the message during the
// signing process.
//
// All options are ignored.
func (e ED25519Signer) SignMessage(message io.Reader, _ ...SignOption) ([]byte, error) {
messageBytes, _, err := ComputeDigestForSigning(message, crypto.Hash(0), ed25519SupportedHashFuncs)
if err != nil {
return nil, err
}
return ed25519.Sign(e.priv, messageBytes), nil
}
// Public returns the public key that can be used to verify signatures created by
// this signer.
func (e ED25519Signer) Public() crypto.PublicKey {
if e.priv == nil {
return nil
}
return e.priv.Public()
}
// PublicKey returns the public key that can be used to verify signatures created by
// this signer. As this value is held in memory, all options provided in arguments
// to this method are ignored.
func (e ED25519Signer) PublicKey(_ ...PublicKeyOption) (crypto.PublicKey, error) {
return e.Public(), nil
}
// Sign computes the signature for the specified message; the first and third arguments to this
// function are ignored as they are not used by the ED25519 algorithm.
func (e ED25519Signer) Sign(_ io.Reader, message []byte, _ crypto.SignerOpts) ([]byte, error) {
if message == nil {
return nil, errors.New("message must not be nil")
}
return e.SignMessage(bytes.NewReader(message))
}
// ED25519Verifier is a signature.Verifier that uses the Ed25519 public-key signature system
type ED25519Verifier struct {
publicKey ed25519.PublicKey
}
// LoadED25519Verifier returns a Verifier that verifies signatures using the specified ED25519 public key.
func LoadED25519Verifier(pub ed25519.PublicKey) (*ED25519Verifier, error) {
if pub == nil {
return nil, errors.New("invalid ED25519 public key specified")
}
return &ED25519Verifier{
publicKey: pub,
}, nil
}
// PublicKey returns the public key that is used to verify signatures by
// this verifier. As this value is held in memory, all options provided in arguments
// to this method are ignored.
func (e *ED25519Verifier) PublicKey(_ ...PublicKeyOption) (crypto.PublicKey, error) {
return e.publicKey, nil
}
// VerifySignature verifies the signature for the given message.
//
// This function returns nil if the verification succeeded, and an error message otherwise.
//
// All options are ignored if specified.
func (e *ED25519Verifier) VerifySignature(signature, message io.Reader, _ ...VerifyOption) error {
messageBytes, _, err := ComputeDigestForVerifying(message, crypto.Hash(0), ed25519SupportedHashFuncs)
if err != nil {
return err
}
if signature == nil {
return errors.New("nil signature passed to VerifySignature")
}
sigBytes, err := io.ReadAll(signature)
if err != nil {
return fmt.Errorf("reading signature: %w", err)
}
if !ed25519.Verify(e.publicKey, messageBytes, sigBytes) {
return errors.New("failed to verify signature")
}
return nil
}
// ED25519SignerVerifier is a signature.SignerVerifier that uses the Ed25519 public-key signature system
type ED25519SignerVerifier struct {
*ED25519Signer
*ED25519Verifier
}
// LoadED25519SignerVerifier creates a combined signer and verifier. This is
// a convenience object that simply wraps an instance of ED25519Signer and ED25519Verifier.
func LoadED25519SignerVerifier(priv ed25519.PrivateKey) (*ED25519SignerVerifier, error) {
signer, err := LoadED25519Signer(priv)
if err != nil {
return nil, fmt.Errorf("initializing signer: %w", err)
}
pub, ok := priv.Public().(ed25519.PublicKey)
if !ok {
return nil, fmt.Errorf("given key is not ed25519.PublicKey: %w", err)
}
verifier, err := LoadED25519Verifier(pub)
if err != nil {
return nil, fmt.Errorf("initializing verifier: %w", err)
}
return &ED25519SignerVerifier{
ED25519Signer: signer,
ED25519Verifier: verifier,
}, nil
}
// NewDefaultED25519SignerVerifier creates a combined signer and verifier using ED25519.
// This creates a new ED25519 key using crypto/rand as an entropy source.
func NewDefaultED25519SignerVerifier() (*ED25519SignerVerifier, ed25519.PrivateKey, error) {
return NewED25519SignerVerifier(rand.Reader)
}
// NewED25519SignerVerifier creates a combined signer and verifier using ED25519.
// This creates a new ED25519 key using the specified entropy source.
func NewED25519SignerVerifier(rand io.Reader) (*ED25519SignerVerifier, ed25519.PrivateKey, error) {
_, priv, err := ed25519.GenerateKey(rand)
if err != nil {
return nil, nil, err
}
sv, err := LoadED25519SignerVerifier(priv)
if err != nil {
return nil, nil, err
}
return sv, priv, nil
}
// PublicKey returns the public key that is used to verify signatures by
// this verifier. As this value is held in memory, all options provided in arguments
// to this method are ignored.
func (e ED25519SignerVerifier) PublicKey(_ ...PublicKeyOption) (crypto.PublicKey, error) {
return e.publicKey, nil
}

View File

@@ -0,0 +1,111 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package signature
import (
"crypto"
crand "crypto/rand"
"errors"
"fmt"
"io"
)
func isSupportedAlg(alg crypto.Hash, supportedAlgs []crypto.Hash) bool {
if supportedAlgs == nil {
return true
}
for _, supportedAlg := range supportedAlgs {
if alg == supportedAlg {
return true
}
}
return false
}
// ComputeDigestForSigning calculates the digest value for the specified message using a hash function selected by the following process:
//
// - if a digest value is already specified in a SignOption and the length of the digest matches that of the selected hash function, the
// digest value will be returned without any further computation
// - if a hash function is given using WithCryptoSignerOpts(opts) as a SignOption, it will be used (if it is in the supported list)
// - otherwise defaultHashFunc will be used (if it is in the supported list)
func ComputeDigestForSigning(rawMessage io.Reader, defaultHashFunc crypto.Hash, supportedHashFuncs []crypto.Hash, opts ...SignOption) (digest []byte, hashedWith crypto.Hash, err error) {
var cryptoSignerOpts crypto.SignerOpts = defaultHashFunc
for _, opt := range opts {
opt.ApplyDigest(&digest)
opt.ApplyCryptoSignerOpts(&cryptoSignerOpts)
}
hashedWith = cryptoSignerOpts.HashFunc()
if !isSupportedAlg(hashedWith, supportedHashFuncs) {
return nil, crypto.Hash(0), fmt.Errorf("unsupported hash algorithm: %q not in %v", hashedWith.String(), supportedHashFuncs)
}
if len(digest) > 0 {
if hashedWith != crypto.Hash(0) && len(digest) != hashedWith.Size() {
err = errors.New("unexpected length of digest for hash function specified")
}
return
}
digest, err = hashMessage(rawMessage, hashedWith)
return
}
// ComputeDigestForVerifying calculates the digest value for the specified message using a hash function selected by the following process:
//
// - if a digest value is already specified in a SignOption and the length of the digest matches that of the selected hash function, the
// digest value will be returned without any further computation
// - if a hash function is given using WithCryptoSignerOpts(opts) as a SignOption, it will be used (if it is in the supported list)
// - otherwise defaultHashFunc will be used (if it is in the supported list)
func ComputeDigestForVerifying(rawMessage io.Reader, defaultHashFunc crypto.Hash, supportedHashFuncs []crypto.Hash, opts ...VerifyOption) (digest []byte, hashedWith crypto.Hash, err error) {
var cryptoSignerOpts crypto.SignerOpts = defaultHashFunc
for _, opt := range opts {
opt.ApplyDigest(&digest)
opt.ApplyCryptoSignerOpts(&cryptoSignerOpts)
}
hashedWith = cryptoSignerOpts.HashFunc()
if !isSupportedAlg(hashedWith, supportedHashFuncs) {
return nil, crypto.Hash(0), fmt.Errorf("unsupported hash algorithm: %q not in %v", hashedWith.String(), supportedHashFuncs)
}
if len(digest) > 0 {
if hashedWith != crypto.Hash(0) && len(digest) != hashedWith.Size() {
err = errors.New("unexpected length of digest for hash function specified")
}
return
}
digest, err = hashMessage(rawMessage, hashedWith)
return
}
func hashMessage(rawMessage io.Reader, hashFunc crypto.Hash) ([]byte, error) {
if rawMessage == nil {
return nil, errors.New("message cannot be nil")
}
if hashFunc == crypto.Hash(0) {
return io.ReadAll(rawMessage)
}
hasher := hashFunc.New()
// avoids reading entire message into memory
if _, err := io.Copy(hasher, rawMessage); err != nil {
return nil, fmt.Errorf("hashing message: %w", err)
}
return hasher.Sum(nil), nil
}
func selectRandFromOpts(opts ...SignOption) io.Reader {
rand := crand.Reader
for _, opt := range opts {
opt.ApplyRand(&rand)
}
return rand
}

View File

@@ -0,0 +1,57 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package signature
import (
"context"
"crypto"
"io"
"github.com/sigstore/sigstore/pkg/signature/options"
)
// RPCOption specifies options to be used when performing RPC
type RPCOption interface {
ApplyContext(*context.Context)
ApplyRemoteVerification(*bool)
ApplyRPCAuthOpts(opts *options.RPCAuth)
ApplyKeyVersion(keyVersion *string)
}
// PublicKeyOption specifies options to be used when obtaining a public key
type PublicKeyOption interface {
RPCOption
}
// MessageOption specifies options to be used when processing messages during signing or verification
type MessageOption interface {
ApplyDigest(*[]byte)
ApplyCryptoSignerOpts(*crypto.SignerOpts)
}
// SignOption specifies options to be used when signing a message
type SignOption interface {
RPCOption
MessageOption
ApplyRand(*io.Reader)
ApplyKeyVersionUsed(**string)
}
// VerifyOption specifies options to be used when verifying a signature
type VerifyOption interface {
RPCOption
MessageOption
}

View File

@@ -0,0 +1,36 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package options
import (
"context"
)
// RequestContext implements the functional option pattern for including a context during RPC
type RequestContext struct {
NoOpOptionImpl
ctx context.Context
}
// ApplyContext sets the specified context as the functional option
func (r RequestContext) ApplyContext(ctx *context.Context) {
*ctx = r.ctx
}
// WithContext specifies that the given context should be used in RPC to external services
func WithContext(ctx context.Context) RequestContext {
return RequestContext{ctx: ctx}
}

View File

@@ -0,0 +1,35 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package options
// RequestDigest implements the functional option pattern for specifying a digest value
type RequestDigest struct {
NoOpOptionImpl
digest []byte
}
// ApplyDigest sets the specified digest value as the functional option
func (r RequestDigest) ApplyDigest(digest *[]byte) {
*digest = r.digest
}
// WithDigest specifies that the given digest can be used by underlying signature implementations
// WARNING: When verifying a digest with ECDSA, it is trivial to craft a valid signature
// over a random message given a public key. Do not use this unles you understand the
// implications and do not need to protect against malleability.
func WithDigest(digest []byte) RequestDigest {
return RequestDigest{digest: digest}
}

View File

@@ -0,0 +1,50 @@
//
// Copyright 2022 The Sigstore Authors.
//
// 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.
package options
// RequestKeyVersion implements the functional option pattern for specifying the KMS key version during signing or verification
type RequestKeyVersion struct {
NoOpOptionImpl
keyVersion string
}
// ApplyKeyVersion sets the KMS's key version as a functional option
func (r RequestKeyVersion) ApplyKeyVersion(keyVersion *string) {
*keyVersion = r.keyVersion
}
// WithKeyVersion specifies that a specific KMS key version be used during signing and verification operations;
// a value of 0 will use the latest version of the key (default)
func WithKeyVersion(keyVersion string) RequestKeyVersion {
return RequestKeyVersion{keyVersion: keyVersion}
}
// RequestKeyVersionUsed implements the functional option pattern for obtaining the KMS key version used during signing
type RequestKeyVersionUsed struct {
NoOpOptionImpl
keyVersionUsed *string
}
// ApplyKeyVersionUsed requests to store the KMS's key version that was used as a functional option
func (r RequestKeyVersionUsed) ApplyKeyVersionUsed(keyVersionUsed **string) {
*keyVersionUsed = r.keyVersionUsed
}
// ReturnKeyVersionUsed specifies that the specific KMS key version that was used during signing should be stored
// in the pointer provided
func ReturnKeyVersionUsed(keyVersionUsed *string) RequestKeyVersionUsed {
return RequestKeyVersionUsed{keyVersionUsed: keyVersionUsed}
}

View File

@@ -0,0 +1,49 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package options
import (
"context"
"crypto"
"io"
)
// NoOpOptionImpl implements the RPCOption, SignOption, VerifyOption interfaces as no-ops.
type NoOpOptionImpl struct{}
// ApplyContext is a no-op required to fully implement the requisite interfaces
func (NoOpOptionImpl) ApplyContext(ctx *context.Context) {}
// ApplyCryptoSignerOpts is a no-op required to fully implement the requisite interfaces
func (NoOpOptionImpl) ApplyCryptoSignerOpts(opts *crypto.SignerOpts) {}
// ApplyDigest is a no-op required to fully implement the requisite interfaces
func (NoOpOptionImpl) ApplyDigest(digest *[]byte) {}
// ApplyRand is a no-op required to fully implement the requisite interfaces
func (NoOpOptionImpl) ApplyRand(rand *io.Reader) {}
// ApplyRemoteVerification is a no-op required to fully implement the requisite interfaces
func (NoOpOptionImpl) ApplyRemoteVerification(remoteVerification *bool) {}
// ApplyRPCAuthOpts is a no-op required to fully implement the requisite interfaces
func (NoOpOptionImpl) ApplyRPCAuthOpts(opts *RPCAuth) {}
// ApplyKeyVersion is a no-op required to fully implement the requisite interfaces
func (NoOpOptionImpl) ApplyKeyVersion(keyVersion *string) {}
// ApplyKeyVersionUsed is a no-op required to fully implement the requisite interfaces
func (NoOpOptionImpl) ApplyKeyVersionUsed(keyVersion **string) {}

View File

@@ -0,0 +1,41 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package options
import (
crand "crypto/rand"
"io"
)
// RequestRand implements the functional option pattern for using a specific source of entropy
type RequestRand struct {
NoOpOptionImpl
rand io.Reader
}
// ApplyRand sets the specified source of entropy as the functional option
func (r RequestRand) ApplyRand(rand *io.Reader) {
*rand = r.rand
}
// WithRand specifies that the given source of entropy should be used in signing operations
func WithRand(rand io.Reader) RequestRand {
r := rand
if r == nil {
r = crand.Reader
}
return RequestRand{rand: r}
}

View File

@@ -0,0 +1,32 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package options
// RequestRemoteVerification implements the functional option pattern for remotely verifiying signatures when possible
type RequestRemoteVerification struct {
NoOpOptionImpl
remoteVerification bool
}
// ApplyRemoteVerification sets remote verification as a functional option
func (r RequestRemoteVerification) ApplyRemoteVerification(remoteVerification *bool) {
*remoteVerification = r.remoteVerification
}
// WithRemoteVerification specifies that the verification operation should be performed remotely (vs in the process of the caller)
func WithRemoteVerification(remoteVerification bool) RequestRemoteVerification {
return RequestRemoteVerification{remoteVerification: remoteVerification}
}

View File

@@ -0,0 +1,58 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package options
// RPCAuthOpts includes authentication settings for RPC calls
type RPCAuthOpts struct {
NoOpOptionImpl
opts RPCAuth
}
// RPCAuth provides credentials for RPC calls, empty fields are ignored
type RPCAuth struct {
Address string // address is the remote server address, e.g. https://vault:8200
Path string // path for the RPC, in vault this is the transit path which default to "transit"
Token string // token used for RPC, in vault this is the VAULT_TOKEN value
OIDC RPCAuthOIDC
}
// RPCAuthOIDC is used to perform the RPC login using OIDC instead of a fixed token
type RPCAuthOIDC struct {
Path string // path defaults to "jwt" for vault
Role string // role is required for jwt logins
Token string // token is a jwt with vault
}
// ApplyRPCAuthOpts sets the RPCAuth as a function option
func (r RPCAuthOpts) ApplyRPCAuthOpts(opts *RPCAuth) {
if r.opts.Address != "" {
opts.Address = r.opts.Address
}
if r.opts.Path != "" {
opts.Path = r.opts.Path
}
if r.opts.Token != "" {
opts.Token = r.opts.Token
}
if r.opts.OIDC.Token != "" {
opts.OIDC = r.opts.OIDC
}
}
// WithRPCAuthOpts specifies RPCAuth settings to be used with RPC logins
func WithRPCAuthOpts(opts RPCAuth) RPCAuthOpts {
return RPCAuthOpts{opts: opts}
}

View File

@@ -0,0 +1,40 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package options
import (
"crypto"
)
// RequestCryptoSignerOpts implements the functional option pattern for supplying crypto.SignerOpts when signing or verifying
type RequestCryptoSignerOpts struct {
NoOpOptionImpl
opts crypto.SignerOpts
}
// ApplyCryptoSignerOpts sets crypto.SignerOpts as a functional option
func (r RequestCryptoSignerOpts) ApplyCryptoSignerOpts(opts *crypto.SignerOpts) {
*opts = r.opts
}
// WithCryptoSignerOpts specifies that provided crypto.SignerOpts be used during signing and verification operations
func WithCryptoSignerOpts(opts crypto.SignerOpts) RequestCryptoSignerOpts {
var optsToUse crypto.SignerOpts = crypto.SHA256
if opts != nil {
optsToUse = opts
}
return RequestCryptoSignerOpts{opts: optsToUse}
}

View File

@@ -0,0 +1,104 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package payload
import (
"encoding/json"
"fmt"
"github.com/google/go-containerregistry/pkg/name"
)
// CosignSignatureType is the value of `critical.type` in a SimpleContainerImage payload.
const CosignSignatureType = "cosign container image signature"
// SimpleContainerImage describes the structure of a basic container image signature payload, as defined at:
// https://github.com/containers/image/blob/master/docs/containers-signature.5.md#json-data-format
type SimpleContainerImage struct {
Critical Critical `json:"critical"` // Critical data critical to correctly evaluating the validity of the signature
Optional map[string]interface{} `json:"optional"` // Optional optional metadata about the image
}
// Critical data critical to correctly evaluating the validity of a signature
type Critical struct {
Identity Identity `json:"identity"` // Identity claimed identity of the image
Image Image `json:"image"` // Image identifies the container that the signature applies to
Type string `json:"type"` // Type must be 'atomic container signature'
}
// Identity is the claimed identity of the image
type Identity struct {
DockerReference string `json:"docker-reference"` // DockerReference is a reference used to refer to or download the image
}
// Image identifies the container image that the signature applies to
type Image struct {
DockerManifestDigest string `json:"docker-manifest-digest"` // DockerManifestDigest the manifest digest of the signed container image
}
// Cosign describes a container image signed using Cosign
type Cosign struct {
Image name.Digest
Annotations map[string]interface{}
}
// SimpleContainerImage returns information about a container image in the github.com/containers/image/signature format
func (p Cosign) SimpleContainerImage() SimpleContainerImage {
return SimpleContainerImage{
Critical: Critical{
Identity: Identity{
DockerReference: p.Image.Repository.Name(),
},
Image: Image{
DockerManifestDigest: p.Image.DigestStr(),
},
Type: CosignSignatureType,
},
Optional: p.Annotations,
}
}
// MarshalJSON marshals the container signature into a []byte of JSON data
func (p Cosign) MarshalJSON() ([]byte, error) {
return json.Marshal(p.SimpleContainerImage())
}
var _ json.Marshaler = Cosign{}
// UnmarshalJSON unmarshals []byte of JSON data into a container signature object
func (p *Cosign) UnmarshalJSON(data []byte) error {
if string(data) == "null" {
// JSON "null" is a no-op by convention
return nil
}
var simple SimpleContainerImage
if err := json.Unmarshal(data, &simple); err != nil {
return err
}
if simple.Critical.Type != CosignSignatureType {
return fmt.Errorf("Cosign signature payload was of an unknown type: %q", simple.Critical.Type)
}
digestStr := simple.Critical.Identity.DockerReference + "@" + simple.Critical.Image.DockerManifestDigest
digest, err := name.NewDigest(digestStr)
if err != nil {
return fmt.Errorf("could not parse image digest string %q: %w", digestStr, err)
}
p.Image = digest
p.Annotations = simple.Optional
return nil
}
var _ json.Unmarshaler = (*Cosign)(nil)

View File

@@ -0,0 +1,25 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package signature
import (
"crypto"
)
// PublicKeyProvider returns a PublicKey associated with a digital signature
type PublicKeyProvider interface {
PublicKey(opts ...PublicKeyOption) (crypto.PublicKey, error)
}

View File

@@ -0,0 +1,225 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package signature
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"errors"
"fmt"
"io"
"github.com/sigstore/sigstore/pkg/signature/options"
)
// RSAPKCS1v15Signer is a signature.Signer that uses the RSA PKCS1v15 algorithm
type RSAPKCS1v15Signer struct {
hashFunc crypto.Hash
priv *rsa.PrivateKey
}
// LoadRSAPKCS1v15Signer calculates signatures using the specified private key and hash algorithm.
//
// hf must be either SHA256, SHA388, or SHA512.
func LoadRSAPKCS1v15Signer(priv *rsa.PrivateKey, hf crypto.Hash) (*RSAPKCS1v15Signer, error) {
if priv == nil {
return nil, errors.New("invalid RSA private key specified")
}
if !isSupportedAlg(hf, rsaSupportedHashFuncs) {
return nil, errors.New("invalid hash function specified")
}
return &RSAPKCS1v15Signer{
priv: priv,
hashFunc: hf,
}, nil
}
// SignMessage signs the provided message using PKCS1v15. If the message is provided,
// this method will compute the digest according to the hash function specified
// when the RSAPKCS1v15Signer was created.
//
// SignMessage recognizes the following Options listed in order of preference:
//
// - WithRand()
//
// - WithDigest()
//
// - WithCryptoSignerOpts()
//
// All other options are ignored if specified.
func (r RSAPKCS1v15Signer) SignMessage(message io.Reader, opts ...SignOption) ([]byte, error) {
digest, hf, err := ComputeDigestForSigning(message, r.hashFunc, rsaSupportedHashFuncs, opts...)
if err != nil {
return nil, err
}
rand := selectRandFromOpts(opts...)
return rsa.SignPKCS1v15(rand, r.priv, hf, digest)
}
// Public returns the public key that can be used to verify signatures created by
// this signer.
func (r RSAPKCS1v15Signer) Public() crypto.PublicKey {
if r.priv == nil {
return nil
}
return r.priv.Public()
}
// PublicKey returns the public key that can be used to verify signatures created by
// this signer. As this value is held in memory, all options provided in arguments
// to this method are ignored.
func (r RSAPKCS1v15Signer) PublicKey(_ ...PublicKeyOption) (crypto.PublicKey, error) {
return r.Public(), nil
}
// Sign computes the signature for the specified digest using PKCS1v15.
//
// If a source of entropy is given in rand, it will be used instead of the default value (rand.Reader
// from crypto/rand).
//
// If opts are specified, they should specify the hash function used to compute digest. If opts are
// not specified, this function assumes the hash function provided when the signer was created was
// used to create the value specified in digest.
func (r RSAPKCS1v15Signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
rsaOpts := []SignOption{options.WithDigest(digest), options.WithRand(rand)}
if opts != nil {
rsaOpts = append(rsaOpts, options.WithCryptoSignerOpts(opts))
}
return r.SignMessage(nil, rsaOpts...)
}
// RSAPKCS1v15Verifier is a signature.Verifier that uses the RSA PKCS1v15 algorithm
type RSAPKCS1v15Verifier struct {
publicKey *rsa.PublicKey
hashFunc crypto.Hash
}
// LoadRSAPKCS1v15Verifier returns a Verifier that verifies signatures using the specified
// RSA public key and hash algorithm.
//
// hf must be either SHA256, SHA388, or SHA512.
func LoadRSAPKCS1v15Verifier(pub *rsa.PublicKey, hashFunc crypto.Hash) (*RSAPKCS1v15Verifier, error) {
if pub == nil {
return nil, errors.New("invalid RSA public key specified")
}
if !isSupportedAlg(hashFunc, rsaSupportedHashFuncs) {
return nil, errors.New("invalid hash function specified")
}
return &RSAPKCS1v15Verifier{
publicKey: pub,
hashFunc: hashFunc,
}, nil
}
// PublicKey returns the public key that is used to verify signatures by
// this verifier. As this value is held in memory, all options provided in arguments
// to this method are ignored.
func (r RSAPKCS1v15Verifier) PublicKey(_ ...PublicKeyOption) (crypto.PublicKey, error) {
return r.publicKey, nil
}
// VerifySignature verifies the signature for the given message using PKCS1v15. Unless provided
// in an option, the digest of the message will be computed using the hash function specified
// when the RSAPKCS1v15Verifier was created.
//
// This function returns nil if the verification succeeded, and an error message otherwise.
//
// This function recognizes the following Options listed in order of preference:
//
// - WithDigest()
//
// - WithCryptoSignerOpts()
//
// All other options are ignored if specified.
func (r RSAPKCS1v15Verifier) VerifySignature(signature, message io.Reader, opts ...VerifyOption) error {
digest, hf, err := ComputeDigestForVerifying(message, r.hashFunc, rsaSupportedVerifyHashFuncs, opts...)
if err != nil {
return err
}
if signature == nil {
return errors.New("nil signature passed to VerifySignature")
}
sigBytes, err := io.ReadAll(signature)
if err != nil {
return fmt.Errorf("reading signature: %w", err)
}
return rsa.VerifyPKCS1v15(r.publicKey, hf, digest, sigBytes)
}
// RSAPKCS1v15SignerVerifier is a signature.SignerVerifier that uses the RSA PKCS1v15 algorithm
type RSAPKCS1v15SignerVerifier struct {
*RSAPKCS1v15Signer
*RSAPKCS1v15Verifier
}
// LoadRSAPKCS1v15SignerVerifier creates a combined signer and verifier. This is a convenience object
// that simply wraps an instance of RSAPKCS1v15Signer and RSAPKCS1v15Verifier.
func LoadRSAPKCS1v15SignerVerifier(priv *rsa.PrivateKey, hf crypto.Hash) (*RSAPKCS1v15SignerVerifier, error) {
signer, err := LoadRSAPKCS1v15Signer(priv, hf)
if err != nil {
return nil, fmt.Errorf("initializing signer: %w", err)
}
verifier, err := LoadRSAPKCS1v15Verifier(&priv.PublicKey, hf)
if err != nil {
return nil, fmt.Errorf("initializing verifier: %w", err)
}
return &RSAPKCS1v15SignerVerifier{
RSAPKCS1v15Signer: signer,
RSAPKCS1v15Verifier: verifier,
}, nil
}
// NewDefaultRSAPKCS1v15SignerVerifier creates a combined signer and verifier using RSA PKCS1v15.
// This creates a new RSA key of 2048 bits and uses the SHA256 hashing algorithm.
func NewDefaultRSAPKCS1v15SignerVerifier() (*RSAPKCS1v15SignerVerifier, *rsa.PrivateKey, error) {
return NewRSAPKCS1v15SignerVerifier(rand.Reader, 2048, crypto.SHA256)
}
// NewRSAPKCS1v15SignerVerifier creates a combined signer and verifier using RSA PKCS1v15.
// This creates a new RSA key of the specified length of bits, entropy source, and hash function.
func NewRSAPKCS1v15SignerVerifier(rand io.Reader, bits int, hashFunc crypto.Hash) (*RSAPKCS1v15SignerVerifier, *rsa.PrivateKey, error) {
priv, err := rsa.GenerateKey(rand, bits)
if err != nil {
return nil, nil, err
}
sv, err := LoadRSAPKCS1v15SignerVerifier(priv, hashFunc)
if err != nil {
return nil, nil, err
}
return sv, priv, nil
}
// PublicKey returns the public key that is used to verify signatures by
// this verifier. As this value is held in memory, all options provided in arguments
// to this method are ignored.
func (r RSAPKCS1v15SignerVerifier) PublicKey(_ ...PublicKeyOption) (crypto.PublicKey, error) {
return r.publicKey, nil
}

View File

@@ -0,0 +1,260 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package signature
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"errors"
"fmt"
"io"
"github.com/sigstore/sigstore/pkg/signature/options"
)
// checked on LoadSigner, LoadVerifier, and SignMessage
var rsaSupportedHashFuncs = []crypto.Hash{
crypto.SHA256,
crypto.SHA384,
crypto.SHA512,
}
// checked on VerifySignature. Supports SHA1 verification.
var rsaSupportedVerifyHashFuncs = []crypto.Hash{
crypto.SHA1,
crypto.SHA256,
crypto.SHA384,
crypto.SHA512,
}
// RSAPSSSigner is a signature.Signer that uses the RSA PSS algorithm
type RSAPSSSigner struct {
hashFunc crypto.Hash
priv *rsa.PrivateKey
pssOpts *rsa.PSSOptions
}
// LoadRSAPSSSigner calculates signatures using the specified private key and hash algorithm.
//
// If opts are specified, then they will be stored and used as a default if not overridden
// by the value passed to Sign().
//
// hf must be either SHA256, SHA388, or SHA512. opts.Hash is ignored.
func LoadRSAPSSSigner(priv *rsa.PrivateKey, hf crypto.Hash, opts *rsa.PSSOptions) (*RSAPSSSigner, error) {
if priv == nil {
return nil, errors.New("invalid RSA private key specified")
}
if !isSupportedAlg(hf, rsaSupportedHashFuncs) {
return nil, errors.New("invalid hash function specified")
}
return &RSAPSSSigner{
priv: priv,
pssOpts: opts,
hashFunc: hf,
}, nil
}
// SignMessage signs the provided message using PSS. If the message is provided,
// this method will compute the digest according to the hash function specified
// when the RSAPSSSigner was created.
//
// This function recognizes the following Options listed in order of preference:
//
// - WithRand()
//
// - WithDigest()
//
// - WithCryptoSignerOpts()
//
// All other options are ignored if specified.
func (r RSAPSSSigner) SignMessage(message io.Reader, opts ...SignOption) ([]byte, error) {
digest, hf, err := ComputeDigestForSigning(message, r.hashFunc, rsaSupportedHashFuncs, opts...)
if err != nil {
return nil, err
}
rand := selectRandFromOpts(opts...)
pssOpts := r.pssOpts
if pssOpts == nil {
pssOpts = &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
}
}
pssOpts.Hash = hf
return rsa.SignPSS(rand, r.priv, hf, digest, pssOpts)
}
// Public returns the public key that can be used to verify signatures created by
// this signer.
func (r RSAPSSSigner) Public() crypto.PublicKey {
if r.priv == nil {
return nil
}
return r.priv.Public()
}
// PublicKey returns the public key that can be used to verify signatures created by
// this signer. As this value is held in memory, all options provided in arguments
// to this method are ignored.
func (r RSAPSSSigner) PublicKey(_ ...PublicKeyOption) (crypto.PublicKey, error) {
return r.Public(), nil
}
// Sign computes the signature for the specified digest using PSS.
//
// If a source of entropy is given in rand, it will be used instead of the default value (rand.Reader
// from crypto/rand).
//
// If opts are specified, they must be *rsa.PSSOptions. If opts are not specified, the hash function
// provided when the signer was created will be assumed.
func (r RSAPSSSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
rsaOpts := []SignOption{options.WithDigest(digest), options.WithRand(rand)}
if opts != nil {
rsaOpts = append(rsaOpts, options.WithCryptoSignerOpts(opts))
}
return r.SignMessage(nil, rsaOpts...)
}
// RSAPSSVerifier is a signature.Verifier that uses the RSA PSS algorithm
type RSAPSSVerifier struct {
publicKey *rsa.PublicKey
hashFunc crypto.Hash
pssOpts *rsa.PSSOptions
}
// LoadRSAPSSVerifier verifies signatures using the specified public key and hash algorithm.
//
// hf must be either SHA256, SHA388, or SHA512. opts.Hash is ignored.
func LoadRSAPSSVerifier(pub *rsa.PublicKey, hashFunc crypto.Hash, opts *rsa.PSSOptions) (*RSAPSSVerifier, error) {
if pub == nil {
return nil, errors.New("invalid RSA public key specified")
}
if !isSupportedAlg(hashFunc, rsaSupportedHashFuncs) {
return nil, errors.New("invalid hash function specified")
}
return &RSAPSSVerifier{
publicKey: pub,
hashFunc: hashFunc,
pssOpts: opts,
}, nil
}
// PublicKey returns the public key that is used to verify signatures by
// this verifier. As this value is held in memory, all options provided in arguments
// to this method are ignored.
func (r RSAPSSVerifier) PublicKey(_ ...PublicKeyOption) (crypto.PublicKey, error) {
return r.publicKey, nil
}
// VerifySignature verifies the signature for the given message using PSS. Unless provided
// in an option, the digest of the message will be computed using the hash function specified
// when the RSAPSSVerifier was created.
//
// This function returns nil if the verification succeeded, and an error message otherwise.
//
// This function recognizes the following Options listed in order of preference:
//
// - WithDigest()
//
// - WithCryptoSignerOpts()
//
// All other options are ignored if specified.
func (r RSAPSSVerifier) VerifySignature(signature, message io.Reader, opts ...VerifyOption) error {
digest, hf, err := ComputeDigestForVerifying(message, r.hashFunc, rsaSupportedVerifyHashFuncs, opts...)
if err != nil {
return err
}
if signature == nil {
return errors.New("nil signature passed to VerifySignature")
}
sigBytes, err := io.ReadAll(signature)
if err != nil {
return fmt.Errorf("reading signature: %w", err)
}
// rsa.VerifyPSS ignores pssOpts.Hash, so we don't set it
pssOpts := r.pssOpts
if pssOpts == nil {
pssOpts = &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
}
}
return rsa.VerifyPSS(r.publicKey, hf, digest, sigBytes, pssOpts)
}
// RSAPSSSignerVerifier is a signature.SignerVerifier that uses the RSA PSS algorithm
type RSAPSSSignerVerifier struct {
*RSAPSSSigner
*RSAPSSVerifier
}
// LoadRSAPSSSignerVerifier creates a combined signer and verifier using RSA PSS. This is
// a convenience object that simply wraps an instance of RSAPSSSigner and RSAPSSVerifier.
func LoadRSAPSSSignerVerifier(priv *rsa.PrivateKey, hf crypto.Hash, opts *rsa.PSSOptions) (*RSAPSSSignerVerifier, error) {
signer, err := LoadRSAPSSSigner(priv, hf, opts)
if err != nil {
return nil, fmt.Errorf("initializing signer: %w", err)
}
verifier, err := LoadRSAPSSVerifier(&priv.PublicKey, hf, opts)
if err != nil {
return nil, fmt.Errorf("initializing verifier: %w", err)
}
return &RSAPSSSignerVerifier{
RSAPSSSigner: signer,
RSAPSSVerifier: verifier,
}, nil
}
// NewDefaultRSAPSSSignerVerifier creates a combined signer and verifier using RSA PSS.
// This creates a new RSA key of 2048 bits and uses the SHA256 hashing algorithm.
func NewDefaultRSAPSSSignerVerifier() (*RSAPSSSignerVerifier, *rsa.PrivateKey, error) {
return NewRSAPSSSignerVerifier(rand.Reader, 2048, crypto.SHA256)
}
// NewRSAPSSSignerVerifier creates a combined signer and verifier using RSA PSS.
// This creates a new RSA key of the specified length of bits, entropy source, and hash function.
func NewRSAPSSSignerVerifier(rand io.Reader, bits int, hashFunc crypto.Hash) (*RSAPSSSignerVerifier, *rsa.PrivateKey, error) {
priv, err := rsa.GenerateKey(rand, bits)
if err != nil {
return nil, nil, err
}
sv, err := LoadRSAPSSSignerVerifier(priv, hashFunc, &rsa.PSSOptions{Hash: hashFunc})
if err != nil {
return nil, nil, err
}
return sv, priv, nil
}
// PublicKey returns the public key that is used to verify signatures by
// this verifier. As this value is held in memory, all options provided in arguments
// to this method are ignored.
func (r RSAPSSSignerVerifier) PublicKey(_ ...PublicKeyOption) (crypto.PublicKey, error) {
return r.publicKey, nil
}

View File

@@ -0,0 +1,89 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package signature
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"errors"
"io"
"io/ioutil"
"path/filepath"
// these ensure we have the implementations loaded
_ "crypto/sha256"
_ "crypto/sha512"
"github.com/sigstore/sigstore/pkg/cryptoutils"
// these ensure we have the implementations loaded
_ "golang.org/x/crypto/sha3"
)
// Signer creates digital signatures over a message using a specified key pair
type Signer interface {
PublicKeyProvider
SignMessage(message io.Reader, opts ...SignOption) ([]byte, error)
}
// SignerOpts implements crypto.SignerOpts but also allows callers to specify
// additional options that may be utilized in signing the digest provided.
type SignerOpts struct {
Hash crypto.Hash
Opts []SignOption
}
// HashFunc returns the hash function for this object
func (s SignerOpts) HashFunc() crypto.Hash {
return s.Hash
}
// LoadSigner returns a signature.Signer based on the algorithm of the private key
// provided.
//
// If privateKey is an RSA key, a RSAPKCS1v15Signer will be returned. If a
// RSAPSSSigner is desired instead, use the LoadRSAPSSSigner() method directly.
func LoadSigner(privateKey crypto.PrivateKey, hashFunc crypto.Hash) (Signer, error) {
switch pk := privateKey.(type) {
case *rsa.PrivateKey:
return LoadRSAPKCS1v15Signer(pk, hashFunc)
case *ecdsa.PrivateKey:
return LoadECDSASigner(pk, hashFunc)
case ed25519.PrivateKey:
return LoadED25519Signer(pk)
}
return nil, errors.New("unsupported public key type")
}
// LoadSignerFromPEMFile returns a signature.Signer based on the algorithm of the private key
// in the file. The Signer will use the hash function specified when computing digests.
//
// If key is an RSA key, a RSAPKCS1v15Signer will be returned. If a
// RSAPSSSigner is desired instead, use the LoadRSAPSSSigner() and
// cryptoutils.UnmarshalPEMToPrivateKey() methods directly.
func LoadSignerFromPEMFile(path string, hashFunc crypto.Hash, pf cryptoutils.PassFunc) (Signer, error) {
fileBytes, err := ioutil.ReadFile(filepath.Clean(path))
if err != nil {
return nil, err
}
priv, err := cryptoutils.UnmarshalPEMToPrivateKey(fileBytes, pf)
if err != nil {
return nil, err
}
return LoadSigner(priv, hashFunc)
}

View File

@@ -0,0 +1,69 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package signature
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"errors"
"io/ioutil"
"path/filepath"
"github.com/sigstore/sigstore/pkg/cryptoutils"
)
// SignerVerifier creates and verifies digital signatures over a message using a specified key pair
type SignerVerifier interface {
Signer
Verifier
}
// LoadSignerVerifier returns a signature.SignerVerifier based on the algorithm of the private key
// provided.
//
// If privateKey is an RSA key, a RSAPKCS1v15SignerVerifier will be returned. If a
// RSAPSSSignerVerifier is desired instead, use the LoadRSAPSSSignerVerifier() method directly.
func LoadSignerVerifier(privateKey crypto.PrivateKey, hashFunc crypto.Hash) (SignerVerifier, error) {
switch pk := privateKey.(type) {
case *rsa.PrivateKey:
return LoadRSAPKCS1v15SignerVerifier(pk, hashFunc)
case *ecdsa.PrivateKey:
return LoadECDSASignerVerifier(pk, hashFunc)
case ed25519.PrivateKey:
return LoadED25519SignerVerifier(pk)
}
return nil, errors.New("unsupported public key type")
}
// LoadSignerVerifierFromPEMFile returns a signature.SignerVerifier based on the algorithm of the private key
// in the file. The SignerVerifier will use the hash function specified when computing digests.
//
// If publicKey is an RSA key, a RSAPKCS1v15SignerVerifier will be returned. If a
// RSAPSSSignerVerifier is desired instead, use the LoadRSAPSSSignerVerifier() and
// cryptoutils.UnmarshalPEMToPrivateKey() methods directly.
func LoadSignerVerifierFromPEMFile(path string, hashFunc crypto.Hash, pf cryptoutils.PassFunc) (SignerVerifier, error) {
fileBytes, err := ioutil.ReadFile(filepath.Clean(path))
if err != nil {
return nil, err
}
priv, err := cryptoutils.UnmarshalPEMToPrivateKey(fileBytes, pf)
if err != nil {
return nil, err
}
return LoadSignerVerifier(priv, hashFunc)
}

View File

@@ -0,0 +1,55 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package signature
import (
"bytes"
"encoding/json"
"fmt"
"github.com/google/go-containerregistry/pkg/name"
sigpayload "github.com/sigstore/sigstore/pkg/signature/payload"
)
// SignImage signs a container manifest using the specified signer object
func SignImage(signer SignerVerifier, image name.Digest, optionalAnnotations map[string]interface{}) (payload, signature []byte, err error) {
imgPayload := sigpayload.Cosign{
Image: image,
Annotations: optionalAnnotations,
}
payload, err = json.Marshal(imgPayload)
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal payload to JSON: %w", err)
}
signature, err = signer.SignMessage(bytes.NewReader(payload))
if err != nil {
return nil, nil, fmt.Errorf("failed to sign payload: %w", err)
}
return payload, signature, nil
}
// VerifyImageSignature verifies a signature over a container manifest
func VerifyImageSignature(signer SignerVerifier, payload, signature []byte) (image name.Digest, annotations map[string]interface{}, err error) {
if err := signer.VerifySignature(bytes.NewReader(signature), bytes.NewReader(payload)); err != nil {
return name.Digest{}, nil, fmt.Errorf("signature verification failed: %w", err)
}
var imgPayload sigpayload.Cosign
if err := json.Unmarshal(payload, &imgPayload); err != nil {
return name.Digest{}, nil, fmt.Errorf("could not deserialize image payload: %w", err)
}
return imgPayload.Image, imgPayload.Annotations, nil
}

View File

@@ -0,0 +1,100 @@
//
// Copyright 2021 The Sigstore Authors.
//
// 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.
package signature
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"errors"
"io"
"io/ioutil"
"path/filepath"
"github.com/sigstore/sigstore/pkg/cryptoutils"
)
// Verifier verifies the digital signature using a specified public key
type Verifier interface {
PublicKeyProvider
VerifySignature(signature, message io.Reader, opts ...VerifyOption) error
}
// LoadVerifier returns a signature.Verifier based on the algorithm of the public key
// provided that will use the hash function specified when computing digests.
//
// If publicKey is an RSA key, a RSAPKCS1v15Verifier will be returned. If a
// RSAPSSVerifier is desired instead, use the LoadRSAPSSVerifier() method directly.
func LoadVerifier(publicKey crypto.PublicKey, hashFunc crypto.Hash) (Verifier, error) {
switch pk := publicKey.(type) {
case *rsa.PublicKey:
return LoadRSAPKCS1v15Verifier(pk, hashFunc)
case *ecdsa.PublicKey:
return LoadECDSAVerifier(pk, hashFunc)
case ed25519.PublicKey:
return LoadED25519Verifier(pk)
}
return nil, errors.New("unsupported public key type")
}
// LoadUnsafeVerifier returns a signature.Verifier based on the algorithm of the public key
// provided that will use SHA1 when computing digests for RSA and ECDSA signatures.
//
// If publicKey is an RSA key, a RSAPKCS1v15Verifier will be returned. If a
// RSAPSSVerifier is desired instead, use the LoadRSAPSSVerifier() method directly.
func LoadUnsafeVerifier(publicKey crypto.PublicKey) (Verifier, error) {
switch pk := publicKey.(type) {
case *rsa.PublicKey:
if pk == nil {
return nil, errors.New("invalid RSA public key specified")
}
return &RSAPKCS1v15Verifier{
publicKey: pk,
hashFunc: crypto.SHA1,
}, nil
case *ecdsa.PublicKey:
if pk == nil {
return nil, errors.New("invalid ECDSA public key specified")
}
return &ECDSAVerifier{
publicKey: pk,
hashFunc: crypto.SHA1,
}, nil
case ed25519.PublicKey:
return LoadED25519Verifier(pk)
}
return nil, errors.New("unsupported public key type")
}
// LoadVerifierFromPEMFile returns a signature.Verifier based on the contents of a
// file located at path. The Verifier wil use the hash function specified when computing digests.
//
// If the publickey is an RSA key, a RSAPKCS1v15Verifier will be returned. If a
// RSAPSSVerifier is desired instead, use the LoadRSAPSSVerifier() and cryptoutils.UnmarshalPEMToPublicKey() methods directly.
func LoadVerifierFromPEMFile(path string, hashFunc crypto.Hash) (Verifier, error) {
fileBytes, err := ioutil.ReadFile(filepath.Clean(path))
if err != nil {
return nil, err
}
pubKey, err := cryptoutils.UnmarshalPEMToPublicKey(fileBytes)
if err != nil {
return nil, err
}
return LoadVerifier(pubKey, hashFunc)
}