//
// 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 implements support for working with encoded certificates, public keys, and private keys
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
remaining = bytes.TrimSpace(remaining)
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
remaining = bytes.TrimSpace(remaining)
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
}
//
// 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,
})
}
//
// 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"
"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)
// Read is for fuzzing
var 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 io.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
}
//
// 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/secure-systems-lab/go-securesystemslib/encrypted"
)
const (
// PrivateKeyPEMType is the string "PRIVATE KEY" to be used during PEM encoding and decoding
PrivateKeyPEMType PEMType = "PRIVATE KEY"
// ECPrivateKeyPEMType is the string "EC PRIVATE KEY" used to parse SEC 1 EC private keys
ECPrivateKeyPEMType PEMType = "EC PRIVATE KEY"
// PKCS1PrivateKeyPEMType is the string "RSA PRIVATE KEY" used to parse PKCS#1-encoded private keys
PKCS1PrivateKeyPEMType PEMType = "RSA 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(PKCS1PrivateKeyPEMType):
return x509.ParsePKCS1PrivateKey(derBlock.Bytes)
case string(ECPrivateKeyPEMType):
return x509.ParseECPrivateKey(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 private key 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 PKCS#8 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
}
//
// 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/ed25519"
"crypto/rsa"
"crypto/sha1" // nolint:gosec
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
)
const (
// PublicKeyPEMType is the string "PUBLIC KEY" to be used during PEM encoding and decoding
PublicKeyPEMType PEMType = "PUBLIC KEY"
// PKCS1PublicKeyPEMType is the string "RSA PUBLIC KEY" used to parse PKCS#1-encoded public keys
PKCS1PublicKeyPEMType PEMType = "RSA 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")
}
switch derBytes.Type {
case string(PublicKeyPEMType):
return x509.ParsePKIXPublicKey(derBytes.Bytes)
case string(PKCS1PublicKeyPEMType):
return x509.ParsePKCS1PublicKey(derBytes.Bytes)
default:
return nil, fmt.Errorf("unknown Public key PEM file type: %v. Are you passing the correct public key?",
derBytes.Type)
}
}
// 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 errors.New(genErrMsg(first, second, "rsa"))
}
case *ecdsa.PublicKey:
if !pub.Equal(second) {
return errors.New(genErrMsg(first, second, "ecdsa"))
}
case ed25519.PublicKey:
if !pub.Equal(second) {
return errors.New(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))
}
//
// Copyright 2025 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/rand"
"encoding/base64"
)
// GenerateRandomURLSafeString generates a cryptographically secure random
// URL-safe string with the specified number of bits of entropy.
func GenerateRandomURLSafeString(entropyLength uint) string {
if entropyLength == 0 {
return ""
}
// Round up to the nearest byte to ensure minimum entropy is met
entropyBytes := (entropyLength + 7) / 8
b := make([]byte, entropyBytes)
_, _ = rand.Read(b)
return base64.RawURLEncoding.EncodeToString(b)
}
// 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 cryptoutils
import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
)
var (
// OIDOtherName is the OID for the OtherName SAN per RFC 5280
OIDOtherName = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 7}
// SANOID is the OID for Subject Alternative Name per RFC 5280
SANOID = asn1.ObjectIdentifier{2, 5, 29, 17}
)
// OtherName describes a name related to a certificate which is not in one
// of the standard name formats. RFC 5280, 4.2.1.6:
//
// OtherName ::= SEQUENCE {
// type-id OBJECT IDENTIFIER,
// value [0] EXPLICIT ANY DEFINED BY type-id }
//
// OtherName for Fulcio-issued certificates only supports UTF-8 strings as values.
type OtherName struct {
ID asn1.ObjectIdentifier
Value string `asn1:"utf8,explicit,tag:0"`
}
// MarshalOtherNameSAN creates a Subject Alternative Name extension
// with an OtherName sequence. RFC 5280, 4.2.1.6:
//
// SubjectAltName ::= GeneralNames
// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
// GeneralName ::= CHOICE {
//
// otherName [0] OtherName,
// ... }
func MarshalOtherNameSAN(name string, critical bool) (*pkix.Extension, error) {
o := OtherName{
ID: OIDOtherName,
Value: name,
}
bytes, err := asn1.MarshalWithParams(o, "tag:0")
if err != nil {
return nil, err
}
sans, err := asn1.Marshal([]asn1.RawValue{{FullBytes: bytes}})
if err != nil {
return nil, err
}
return &pkix.Extension{
Id: SANOID,
Critical: critical,
Value: sans,
}, nil
}
// UnmarshalOtherNameSAN extracts a UTF-8 string from the OtherName
// field in the Subject Alternative Name extension.
func UnmarshalOtherNameSAN(exts []pkix.Extension) (string, error) {
var otherNames []string
for _, e := range exts {
if !e.Id.Equal(SANOID) {
continue
}
var seq asn1.RawValue
rest, err := asn1.Unmarshal(e.Value, &seq)
if err != nil {
return "", err
} else if len(rest) != 0 {
return "", fmt.Errorf("trailing data after X.509 extension")
}
if !seq.IsCompound || seq.Tag != asn1.TagSequence || seq.Class != asn1.ClassUniversal {
return "", asn1.StructuralError{Msg: "bad SAN sequence"}
}
rest = seq.Bytes
for len(rest) > 0 {
var v asn1.RawValue
rest, err = asn1.Unmarshal(rest, &v)
if err != nil {
return "", err
}
// skip all GeneralName fields except OtherName
if v.Tag != 0 {
continue
}
var other OtherName
if _, err := asn1.UnmarshalWithParams(v.FullBytes, &other, "tag:0"); err != nil {
return "", fmt.Errorf("could not parse requested OtherName SAN: %w", err)
}
if !other.ID.Equal(OIDOtherName) {
return "", fmt.Errorf("unexpected OID for OtherName, expected %v, got %v", OIDOtherName, other.ID)
}
otherNames = append(otherNames, other.Value)
}
}
if len(otherNames) == 0 {
return "", errors.New("no OtherName found")
}
if len(otherNames) != 1 {
return "", errors.New("expected only one OtherName")
}
return otherNames[0], nil
}
// GetSubjectAlternateNames extracts all subject alternative names from
// the certificate, including email addresses, DNS, IP addresses, URIs,
// and OtherName SANs
func GetSubjectAlternateNames(cert *x509.Certificate) []string {
sans := []string{}
if cert == nil {
return sans
}
sans = append(sans, cert.DNSNames...)
sans = append(sans, cert.EmailAddresses...)
for _, ip := range cert.IPAddresses {
sans = append(sans, ip.String())
}
for _, uri := range cert.URIs {
sans = append(sans, uri.String())
}
// ignore error if there's no OtherName SAN
otherName, _ := UnmarshalOtherNameSAN(cert.Extensions)
if len(otherName) > 0 {
sans = append(sans, otherName)
}
return sans
}
//
// Copyright 2024 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/elliptic"
"crypto/rsa"
"errors"
"fmt"
v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1"
)
// PublicKeyType represents the public key algorithm for a given signature algorithm.
type PublicKeyType uint
const (
// RSA public key
RSA PublicKeyType = iota
// ECDSA public key
ECDSA
// ED25519 public key
ED25519
)
// RSAKeySize represents the size of an RSA public key in bits.
type RSAKeySize int
// AlgorithmDetails exposes relevant information for a given signature algorithm.
type AlgorithmDetails struct {
// knownAlgorithm is the signature algorithm that the following details refer to.
knownAlgorithm v1.PublicKeyDetails
// keyType is the public key algorithm being used.
keyType PublicKeyType
// hashType is the hash algorithm being used.
hashType crypto.Hash
// protoHashType is the hash algorithm being used as a proto message, it must be the protobuf-specs
// v1.HashAlgorithm equivalent of the hashType.
protoHashType v1.HashAlgorithm
// extraKeyParams contains any extra parameters required to check a given public key against this entry.
//
// The underlying type of these parameters is dependent on the keyType.
// For example, ECDSA algorithms will store an elliptic curve here whereas, RSA keys will store the key size.
// Algorithms that don't require any extra parameters leave this set to nil.
extraKeyParams interface{}
// flagValue is a string representation of the signature algorithm that follows the naming conventions of CLI
// arguments that are used for Sigstore services.
flagValue string
}
// GetSignatureAlgorithm returns the PublicKeyDetails associated with the algorithm details.
func (a AlgorithmDetails) GetSignatureAlgorithm() v1.PublicKeyDetails {
return a.knownAlgorithm
}
// GetKeyType returns the PublicKeyType for the algorithm details.
func (a AlgorithmDetails) GetKeyType() PublicKeyType {
return a.keyType
}
// GetHashType returns the hash algorithm that should be used with this algorithm.
func (a AlgorithmDetails) GetHashType() crypto.Hash {
return a.hashType
}
// GetProtoHashType is a convenience method to get the protobuf-specs type of the hash algorithm.
func (a AlgorithmDetails) GetProtoHashType() v1.HashAlgorithm {
return a.protoHashType
}
// GetRSAKeySize returns the RSA key size for the algorithm details, if the key type is RSA.
func (a AlgorithmDetails) GetRSAKeySize() (RSAKeySize, error) {
if a.keyType != RSA {
return 0, fmt.Errorf("unable to retrieve RSA key size for key type: %T", a.keyType)
}
rsaKeySize, ok := a.extraKeyParams.(RSAKeySize)
if !ok {
// This should be unreachable.
return 0, fmt.Errorf("unable to retrieve key size for RSA, malformed algorithm details?: %T", a.keyType)
}
return rsaKeySize, nil
}
// GetECDSACurve returns the elliptic curve for the algorithm details, if the key type is ECDSA.
func (a AlgorithmDetails) GetECDSACurve() (*elliptic.Curve, error) {
if a.keyType != ECDSA {
return nil, fmt.Errorf("unable to retrieve ECDSA curve for key type: %T", a.keyType)
}
ecdsaCurve, ok := a.extraKeyParams.(elliptic.Curve)
if !ok {
// This should be unreachable.
return nil, fmt.Errorf("unable to retrieve curve for ECDSA, malformed algorithm details?: %T", a.keyType)
}
return &ecdsaCurve, nil
}
func (a AlgorithmDetails) checkKey(pubKey crypto.PublicKey) (bool, error) {
switch a.keyType {
case RSA:
rsaKey, ok := pubKey.(*rsa.PublicKey)
if !ok {
return false, nil
}
keySize, err := a.GetRSAKeySize()
if err != nil {
return false, err
}
return rsaKey.Size()*8 == int(keySize), nil
case ECDSA:
ecdsaKey, ok := pubKey.(*ecdsa.PublicKey)
if !ok {
return false, nil
}
curve, err := a.GetECDSACurve()
if err != nil {
return false, err
}
return ecdsaKey.Curve == *curve, nil
case ED25519:
_, ok := pubKey.(ed25519.PublicKey)
return ok, nil
}
return false, fmt.Errorf("unrecognized key type: %T", a.keyType)
}
func (a AlgorithmDetails) checkHash(hashType crypto.Hash) bool {
return a.hashType == hashType
}
// Note that deprecated options in PublicKeyDetails are not included in this
// list, including PKCS1v1.5 encoded RSA. Refer to the v1.PublicKeyDetails enum
// for more details.
var supportedAlgorithms = []AlgorithmDetails{
{v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_2048_SHA256, RSA, crypto.SHA256, v1.HashAlgorithm_SHA2_256, RSAKeySize(2048), "rsa-sign-pkcs1-2048-sha256"},
{v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_3072_SHA256, RSA, crypto.SHA256, v1.HashAlgorithm_SHA2_256, RSAKeySize(3072), "rsa-sign-pkcs1-3072-sha256"},
{v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_4096_SHA256, RSA, crypto.SHA256, v1.HashAlgorithm_SHA2_256, RSAKeySize(4096), "rsa-sign-pkcs1-4096-sha256"},
{v1.PublicKeyDetails_PKIX_RSA_PSS_2048_SHA256, RSA, crypto.SHA256, v1.HashAlgorithm_SHA2_256, RSAKeySize(2048), "rsa-sign-pss-2048-sha256"},
{v1.PublicKeyDetails_PKIX_RSA_PSS_3072_SHA256, RSA, crypto.SHA256, v1.HashAlgorithm_SHA2_256, RSAKeySize(3072), "rsa-sign-pss-3072-sha256"},
{v1.PublicKeyDetails_PKIX_RSA_PSS_4096_SHA256, RSA, crypto.SHA256, v1.HashAlgorithm_SHA2_256, RSAKeySize(4096), "rsa-sign-pss-4092-sha256"},
{v1.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256, ECDSA, crypto.SHA256, v1.HashAlgorithm_SHA2_256, elliptic.P256(), "ecdsa-sha2-256-nistp256"},
{v1.PublicKeyDetails_PKIX_ECDSA_P384_SHA_384, ECDSA, crypto.SHA384, v1.HashAlgorithm_SHA2_384, elliptic.P384(), "ecdsa-sha2-384-nistp384"},
{v1.PublicKeyDetails_PKIX_ECDSA_P384_SHA_256, ECDSA, crypto.SHA256, v1.HashAlgorithm_SHA2_256, elliptic.P384(), "ecdsa-sha2-256-nistp384"}, //nolint:staticcheck
{v1.PublicKeyDetails_PKIX_ECDSA_P521_SHA_512, ECDSA, crypto.SHA512, v1.HashAlgorithm_SHA2_512, elliptic.P521(), "ecdsa-sha2-512-nistp521"},
{v1.PublicKeyDetails_PKIX_ECDSA_P521_SHA_256, ECDSA, crypto.SHA256, v1.HashAlgorithm_SHA2_256, elliptic.P521(), "ecdsa-sha2-256-nistp521"}, //nolint:staticcheck
{v1.PublicKeyDetails_PKIX_ED25519, ED25519, crypto.Hash(0), v1.HashAlgorithm_HASH_ALGORITHM_UNSPECIFIED, nil, "ed25519"},
{v1.PublicKeyDetails_PKIX_ED25519_PH, ED25519, crypto.SHA512, v1.HashAlgorithm_SHA2_512, nil, "ed25519-ph"},
}
// AlgorithmRegistryConfig represents a set of permitted algorithms for a given Sigstore service or component.
//
// Individual services may wish to restrict what algorithms are allowed to a subset of what is covered in the algorithm
// registry (represented by v1.PublicKeyDetails).
type AlgorithmRegistryConfig struct {
permittedAlgorithms []AlgorithmDetails
}
// GetAlgorithmDetails retrieves a set of details for a given v1.PublicKeyDetails flag that allows users to
// introspect the public key algorithm, hash algorithm and more.
func GetAlgorithmDetails(knownSignatureAlgorithm v1.PublicKeyDetails) (AlgorithmDetails, error) {
for _, detail := range supportedAlgorithms {
if detail.knownAlgorithm == knownSignatureAlgorithm {
return detail, nil
}
}
return AlgorithmDetails{}, fmt.Errorf("could not find algorithm details for known signature algorithm: %s", knownSignatureAlgorithm)
}
// NewAlgorithmRegistryConfig creates a new AlgorithmRegistryConfig for a set of permitted signature algorithms.
func NewAlgorithmRegistryConfig(algorithmConfig []v1.PublicKeyDetails) (*AlgorithmRegistryConfig, error) {
permittedAlgorithms := make([]AlgorithmDetails, 0, len(supportedAlgorithms))
for _, algorithm := range algorithmConfig {
a, err := GetAlgorithmDetails(algorithm)
if err != nil {
return nil, err
}
permittedAlgorithms = append(permittedAlgorithms, a)
}
return &AlgorithmRegistryConfig{permittedAlgorithms: permittedAlgorithms}, nil
}
// IsAlgorithmPermitted checks whether a given public key/hash algorithm combination is permitted by a registry config.
func (registryConfig AlgorithmRegistryConfig) IsAlgorithmPermitted(key crypto.PublicKey, hash crypto.Hash) (bool, error) {
for _, algorithm := range registryConfig.permittedAlgorithms {
keyMatch, err := algorithm.checkKey(key)
if err != nil {
return false, err
}
if keyMatch && algorithm.checkHash(hash) {
return true, nil
}
}
return false, nil
}
// FormatSignatureAlgorithmFlag formats a v1.PublicKeyDetails to a string that conforms to the naming conventions
// of CLI arguments that are used for Sigstore services.
func FormatSignatureAlgorithmFlag(algorithm v1.PublicKeyDetails) (string, error) {
for _, a := range supportedAlgorithms {
if a.knownAlgorithm == algorithm {
return a.flagValue, nil
}
}
return "", fmt.Errorf("could not find matching flag for signature algorithm: %s", algorithm)
}
// ParseSignatureAlgorithmFlag parses a string produced by FormatSignatureAlgorithmFlag and returns the corresponding
// v1.PublicKeyDetails value.
func ParseSignatureAlgorithmFlag(flag string) (v1.PublicKeyDetails, error) {
for _, a := range supportedAlgorithms {
if a.flagValue == flag {
return a.knownAlgorithm, nil
}
}
return v1.PublicKeyDetails_PUBLIC_KEY_DETAILS_UNSPECIFIED, fmt.Errorf("could not find matching signature algorithm for flag: %s", flag)
}
// GetDefaultPublicKeyDetails returns the default public key details for a given key.
//
// RSA 2048 => v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_2048_SHA256
// RSA 3072 => v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_3072_SHA256
// RSA 4096 => v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_4096_SHA256
// ECDSA P256 => v1.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256
// ECDSA P384 => v1.PublicKeyDetails_PKIX_ECDSA_P384_SHA_384
// ECDSA P521 => v1.PublicKeyDetails_PKIX_ECDSA_P521_SHA_512
// ED25519 => v1.PublicKeyDetails_PKIX_ED25519_PH
//
// This function accepts LoadOptions, which are used to determine the default
// public key details when there may be ambiguities. For example, RSA keys may
// be PSS or PKCS1v1.5 encoded, and ED25519 keys may be used with PureEd25519 or
// with Ed25519ph. The Hash option is ignored if passed, because each of the
// supported algorithms already has a default hash.
func GetDefaultPublicKeyDetails(publicKey crypto.PublicKey, opts ...LoadOption) (v1.PublicKeyDetails, error) {
var rsaPSSOptions *rsa.PSSOptions
var useED25519ph bool
for _, o := range opts {
o.ApplyED25519ph(&useED25519ph)
o.ApplyRSAPSS(&rsaPSSOptions)
}
switch pk := publicKey.(type) {
case *rsa.PublicKey:
if rsaPSSOptions != nil {
switch pk.Size() * 8 {
case 2048:
return v1.PublicKeyDetails_PKIX_RSA_PSS_2048_SHA256, nil
case 3072:
return v1.PublicKeyDetails_PKIX_RSA_PSS_3072_SHA256, nil
case 4096:
return v1.PublicKeyDetails_PKIX_RSA_PSS_4096_SHA256, nil
}
} else {
switch pk.Size() * 8 {
case 2048:
return v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_2048_SHA256, nil
case 3072:
return v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_3072_SHA256, nil
case 4096:
return v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_4096_SHA256, nil
}
}
case *ecdsa.PublicKey:
switch pk.Curve {
case elliptic.P256():
return v1.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256, nil
case elliptic.P384():
return v1.PublicKeyDetails_PKIX_ECDSA_P384_SHA_384, nil
case elliptic.P521():
return v1.PublicKeyDetails_PKIX_ECDSA_P521_SHA_512, nil
}
case ed25519.PublicKey:
if useED25519ph {
return v1.PublicKeyDetails_PKIX_ED25519_PH, nil
}
return v1.PublicKeyDetails_PKIX_ED25519, nil
}
return v1.PublicKeyDetails_PUBLIC_KEY_DETAILS_UNSPECIFIED, errors.New("unsupported public key type")
}
// GetDefaultAlgorithmDetails returns the default algorithm details for a given
// key, according to GetDefaultPublicKeyDetails.
//
// This function accepts LoadOptions, which are used to determine the default
// algorithm details when there may be ambiguities. For example, RSA keys may be
// PSS or PKCS1v1.5 encoded, and ED25519 keys may be used with PureEd25519 or
// with Ed25519ph. The Hash option is ignored if passed, because each of the
// supported algorithms already has a default hash.
func GetDefaultAlgorithmDetails(publicKey crypto.PublicKey, opts ...LoadOption) (AlgorithmDetails, error) {
knownAlgorithm, err := GetDefaultPublicKeyDetails(publicKey, opts...)
if err != nil {
return AlgorithmDetails{}, err
}
return GetAlgorithmDetails(knownAlgorithm)
}
//
// 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"
"encoding/asn1"
"errors"
"fmt"
"io"
"math/big"
"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 {
if e.publicKey == nil {
return errors.New("no public key set for ECDSAVerifier")
}
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)
}
// Without this check, VerifyASN1 panics on an invalid key.
if !e.publicKey.IsOnCurve(e.publicKey.X, e.publicKey.Y) {
return fmt.Errorf("invalid ECDSA public key for %s", e.publicKey.Params().Name)
}
asnParseTest := struct {
R, S *big.Int
}{}
if _, err := asn1.Unmarshal(sigBytes, &asnParseTest); err == nil {
if !ecdsa.VerifyASN1(e.publicKey, digest, sigBytes) {
return errors.New("invalid signature when validating ASN.1 encoded signature")
}
} else {
// deal with IEEE P1363 encoding of signatures
if len(sigBytes) == 0 || len(sigBytes) > 132 || len(sigBytes)%2 != 0 {
return errors.New("ecdsa: Invalid IEEE_P1363 encoded bytes")
}
r := new(big.Int).SetBytes(sigBytes[:len(sigBytes)/2])
s := new(big.Int).SetBytes(sigBytes[len(sigBytes)/2:])
if !ecdsa.Verify(e.publicKey, digest, r, s) {
return errors.New("invalid signature when validating IEEE_P1363 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
}
//
// 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")
}
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
}
//
// Copyright 2024 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/ed25519"
"crypto/rand"
"errors"
"fmt"
"io"
"github.com/sigstore/sigstore/pkg/signature/options"
)
var ed25519phSupportedHashFuncs = []crypto.Hash{
crypto.SHA512,
}
// ED25519phSigner is a signature.Signer that uses the Ed25519 public-key signature system with pre-hashing
type ED25519phSigner struct {
priv ed25519.PrivateKey
}
// LoadED25519phSigner calculates signatures using the specified private key.
func LoadED25519phSigner(priv ed25519.PrivateKey) (*ED25519phSigner, error) {
if priv == nil {
return nil, errors.New("invalid ED25519 private key specified")
}
return &ED25519phSigner{
priv: priv,
}, nil
}
// ToED25519SignerVerifier creates a ED25519SignerVerifier from a ED25519phSignerVerifier
//
// Clients that use ED25519phSignerVerifier should use this method to get a
// SignerVerifier that uses the same ED25519 private key, but with the Pure
// Ed25519 algorithm. This might be necessary to interact with Fulcio, which
// only supports the Pure Ed25519 algorithm.
func (e ED25519phSignerVerifier) ToED25519SignerVerifier() (*ED25519SignerVerifier, error) {
return LoadED25519SignerVerifier(e.priv)
}
// SignMessage signs the provided message. If the message is provided,
// this method will compute the digest according to the hash function specified
// when the ED25519phSigner was created.
//
// This function recognizes the following Options listed in order of preference:
//
// - WithDigest()
//
// All other options are ignored if specified.
func (e ED25519phSigner) SignMessage(message io.Reader, opts ...SignOption) ([]byte, error) {
digest, _, err := ComputeDigestForSigning(message, crypto.SHA512, ed25519phSupportedHashFuncs, opts...)
if err != nil {
return nil, err
}
return e.priv.Sign(nil, digest, crypto.SHA512)
}
// Public returns the public key that can be used to verify signatures created by
// this signer.
func (e ED25519phSigner) 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 ED25519phSigner) 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 ED25519ph algorithm.
func (e ED25519phSigner) Sign(_ io.Reader, digest []byte, _ crypto.SignerOpts) ([]byte, error) {
return e.SignMessage(nil, options.WithDigest(digest))
}
// ED25519phVerifier is a signature.Verifier that uses the Ed25519 public-key signature system
type ED25519phVerifier struct {
publicKey ed25519.PublicKey
}
// LoadED25519phVerifier returns a Verifier that verifies signatures using the
// specified ED25519 public key.
func LoadED25519phVerifier(pub ed25519.PublicKey) (*ED25519phVerifier, error) {
if pub == nil {
return nil, errors.New("invalid ED25519 public key specified")
}
return &ED25519phVerifier{
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 *ED25519phVerifier) 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 ED25519phVerifier 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 *ED25519phVerifier) VerifySignature(signature, message io.Reader, opts ...VerifyOption) error {
if signature == nil {
return errors.New("nil signature passed to VerifySignature")
}
digest, _, err := ComputeDigestForVerifying(message, crypto.SHA512, ed25519phSupportedHashFuncs, opts...)
if err != nil {
return err
}
sigBytes, err := io.ReadAll(signature)
if err != nil {
return fmt.Errorf("reading signature: %w", err)
}
if err := ed25519.VerifyWithOptions(e.publicKey, digest, sigBytes, &ed25519.Options{Hash: crypto.SHA512}); err != nil {
return fmt.Errorf("failed to verify signature: %w", err)
}
return nil
}
// ED25519phSignerVerifier is a signature.SignerVerifier that uses the Ed25519 public-key signature system
type ED25519phSignerVerifier struct {
*ED25519phSigner
*ED25519phVerifier
}
// LoadED25519phSignerVerifier creates a combined signer and verifier. This is
// a convenience object that simply wraps an instance of ED25519phSigner and ED25519phVerifier.
func LoadED25519phSignerVerifier(priv ed25519.PrivateKey) (*ED25519phSignerVerifier, error) {
signer, err := LoadED25519phSigner(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")
}
verifier, err := LoadED25519phVerifier(pub)
if err != nil {
return nil, fmt.Errorf("initializing verifier: %w", err)
}
return &ED25519phSignerVerifier{
ED25519phSigner: signer,
ED25519phVerifier: verifier,
}, nil
}
// NewDefaultED25519phSignerVerifier creates a combined signer and verifier using ED25519.
// This creates a new ED25519 key using crypto/rand as an entropy source.
func NewDefaultED25519phSignerVerifier() (*ED25519phSignerVerifier, ed25519.PrivateKey, error) {
return NewED25519phSignerVerifier(rand.Reader)
}
// NewED25519phSignerVerifier creates a combined signer and verifier using ED25519.
// This creates a new ED25519 key using the specified entropy source.
func NewED25519phSignerVerifier(rand io.Reader) (*ED25519phSignerVerifier, ed25519.PrivateKey, error) {
_, priv, err := ed25519.GenerateKey(rand)
if err != nil {
return nil, nil, err
}
sv, err := LoadED25519phSignerVerifier(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 ED25519phSignerVerifier) PublicKey(_ ...PublicKeyOption) (crypto.PublicKey, error) {
return e.publicKey, nil
}
//
// 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, hashedWith, err
}
digest, err = hashMessage(rawMessage, hashedWith)
return digest, hashedWith, err
}
// 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, hashedWith, err
}
digest, err = hashMessage(rawMessage, hashedWith)
return digest, hashedWith, err
}
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
}
//
// 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 defines options for KMS clients
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}
}
//
// 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}
}
//
// 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}
}
//
// Copyright 2024 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"
"crypto/rsa"
)
// RequestHash implements the functional option pattern for setting a Hash
// function when loading a signer or verifier
type RequestHash struct {
NoOpOptionImpl
hashFunc crypto.Hash
}
// ApplyHash sets the hash as requested by the functional option
func (r RequestHash) ApplyHash(hash *crypto.Hash) {
*hash = r.hashFunc
}
// WithHash specifies that the given hash function should be used when loading a signer or verifier
func WithHash(hash crypto.Hash) RequestHash {
return RequestHash{hashFunc: hash}
}
// RequestED25519ph implements the functional option pattern for specifying
// ED25519ph (pre-hashed) should be used when loading a signer or verifier and a
// ED25519 key is
type RequestED25519ph struct {
NoOpOptionImpl
useED25519ph bool
}
// ApplyED25519ph sets the ED25519ph flag as requested by the functional option
func (r RequestED25519ph) ApplyED25519ph(useED25519ph *bool) {
*useED25519ph = r.useED25519ph
}
// WithED25519ph specifies that the ED25519ph algorithm should be used when a ED25519 key is used
func WithED25519ph() RequestED25519ph {
return RequestED25519ph{useED25519ph: true}
}
// RequestPSSOptions implements the functional option pattern for specifying RSA
// PSS should be used when loading a signer or verifier and a RSA key is
// detected
type RequestPSSOptions struct {
NoOpOptionImpl
opts *rsa.PSSOptions
}
// ApplyRSAPSS sets the RSAPSS options as requested by the functional option
func (r RequestPSSOptions) ApplyRSAPSS(opts **rsa.PSSOptions) {
*opts = r.opts
}
// WithRSAPSS specifies that the RSAPSS algorithm should be used when a RSA key is used
// Note that the RSA PSSOptions contains an hash algorithm, which will override
// the hash function specified with WithHash.
func WithRSAPSS(opts *rsa.PSSOptions) RequestPSSOptions {
return RequestPSSOptions{opts: opts}
}
//
// 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"
"crypto/rsa"
"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(_ *context.Context) {}
// ApplyCryptoSignerOpts is a no-op required to fully implement the requisite interfaces
func (NoOpOptionImpl) ApplyCryptoSignerOpts(_ *crypto.SignerOpts) {}
// ApplyDigest is a no-op required to fully implement the requisite interfaces
func (NoOpOptionImpl) ApplyDigest(_ *[]byte) {}
// ApplyRand is a no-op required to fully implement the requisite interfaces
func (NoOpOptionImpl) ApplyRand(_ *io.Reader) {}
// ApplyRemoteVerification is a no-op required to fully implement the requisite interfaces
func (NoOpOptionImpl) ApplyRemoteVerification(_ *bool) {}
// ApplyRPCAuthOpts is a no-op required to fully implement the requisite interfaces
func (NoOpOptionImpl) ApplyRPCAuthOpts(_ *RPCAuth) {}
// ApplyKeyVersion is a no-op required to fully implement the requisite interfaces
func (NoOpOptionImpl) ApplyKeyVersion(_ *string) {}
// ApplyKeyVersionUsed is a no-op required to fully implement the requisite interfaces
func (NoOpOptionImpl) ApplyKeyVersionUsed(_ **string) {}
// ApplyHash is a no-op required to fully implement the requisite interfaces
func (NoOpOptionImpl) ApplyHash(_ *crypto.Hash) {}
// ApplyED25519ph is a no-op required to fully implement the requisite interfaces
func (NoOpOptionImpl) ApplyED25519ph(_ *bool) {}
// ApplyRSAPSS is a no-op required to fully implement the requisite interfaces
func (NoOpOptionImpl) ApplyRSAPSS(_ **rsa.PSSOptions) {}
//
// 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}
}
//
// 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}
}
//
// 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}
}
//
// 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}
}
//
// 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 defines a container image
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/main/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
// ClaimedIdentity is what the signer claims the image to be; usually a registry.com/…/repo:tag, but can also use a digest instead.
// ALMOST ALL consumers MUST verify that ClaimedIdentity in the signature is correct given how user refers to the image;
// e.g. if the user asks to access a signed image example.com/repo/mysql:3.14,
// it is ALMOST ALWAYS necessary to validate that ClaimedIdentity = example.com/repo/mysql:3.14
//
// Considerations:
// - The user might refer to an image using a digest (example.com/repo/mysql@sha256:…); in that case the registry/…/repo should still match
// - If the image is multi-arch, ClaimedIdentity usually refers to the top-level multi-arch image index also on the per-arch images
// (possibly even if ClaimedIdentity contains a digest!)
// - Older versions of cosign generate signatures where ClaimedIdentity only contains a registry/…/repo ; signature consumers should allow users
// to determine whether such images should be accepted (and, long-term, the default SHOULD be to reject them)
ClaimedIdentity string
Annotations map[string]interface{}
}
// SimpleContainerImage returns information about a container image in the github.com/containers/image/signature format
func (p Cosign) SimpleContainerImage() SimpleContainerImage {
dockerReference := p.Image.Repository.Name()
if p.ClaimedIdentity != "" {
dockerReference = p.ClaimedIdentity
}
return SimpleContainerImage{
Critical: Critical{
Identity: Identity{
DockerReference: dockerReference,
},
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.ClaimedIdentity = simple.Critical.Identity.DockerReference
p.Annotations = simple.Optional
return nil
}
var _ json.Unmarshaler = (*Cosign)(nil)
//
// 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
}
//
// 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
}
//
// 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"
"os"
"path/filepath"
// these ensure we have the implementations loaded
_ "crypto/sha256"
_ "crypto/sha512"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature/options"
)
// 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) {
return LoadSignerWithOpts(privateKey, options.WithHash(hashFunc))
}
// LoadSignerWithOpts returns a signature.Signer based on the algorithm of the private key
// provided.
func LoadSignerWithOpts(privateKey crypto.PrivateKey, opts ...LoadOption) (Signer, error) {
var rsaPSSOptions *rsa.PSSOptions
var useED25519ph bool
hashFunc := crypto.SHA256
for _, o := range opts {
o.ApplyED25519ph(&useED25519ph)
o.ApplyHash(&hashFunc)
o.ApplyRSAPSS(&rsaPSSOptions)
}
switch pk := privateKey.(type) {
case *rsa.PrivateKey:
if rsaPSSOptions != nil {
return LoadRSAPSSSigner(pk, hashFunc, rsaPSSOptions)
}
return LoadRSAPKCS1v15Signer(pk, hashFunc)
case *ecdsa.PrivateKey:
return LoadECDSASigner(pk, hashFunc)
case ed25519.PrivateKey:
if useED25519ph {
return LoadED25519phSigner(pk)
}
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 := os.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)
}
// LoadSignerFromPEMFileWithOpts returns a signature.Signer based on the algorithm of the private key
// in the file. The Signer will use the hash function specified in the options when computing digests.
func LoadSignerFromPEMFileWithOpts(path string, pf cryptoutils.PassFunc, opts ...LoadOption) (Signer, error) {
fileBytes, err := os.ReadFile(filepath.Clean(path))
if err != nil {
return nil, err
}
priv, err := cryptoutils.UnmarshalPEMToPrivateKey(fileBytes, pf)
if err != nil {
return nil, err
}
return LoadSignerWithOpts(priv, opts...)
}
// LoadDefaultSigner returns a signature.Signer based on the private key.
// Each private key has a corresponding PublicKeyDetails associated in the
// Sigstore ecosystem, see Algorithm Registry for more details.
func LoadDefaultSigner(privateKey crypto.PrivateKey, opts ...LoadOption) (Signer, error) {
signer, ok := privateKey.(crypto.Signer)
if !ok {
return nil, errors.New("private key does not implement signature.Signer")
}
algorithmDetails, err := GetDefaultAlgorithmDetails(signer.Public(), opts...)
if err != nil {
return nil, err
}
return LoadSignerFromAlgorithmDetails(privateKey, algorithmDetails, opts...)
}
// LoadSignerFromAlgorithmDetails returns a signature.Signer based on
// the algorithm details and the user's choice of options.
func LoadSignerFromAlgorithmDetails(privateKey crypto.PrivateKey, algorithmDetails AlgorithmDetails, opts ...LoadOption) (Signer, error) {
filteredOpts := GetOptsFromAlgorithmDetails(algorithmDetails, opts...)
return LoadSignerWithOpts(privateKey, filteredOpts...)
}
//
// 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"
"os"
"path/filepath"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature/options"
)
// 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) {
return LoadSignerVerifierWithOpts(privateKey, options.WithHash(hashFunc))
}
// LoadSignerVerifierWithOpts returns a signature.SignerVerifier based on the
// algorithm of the private key provided and the user's choice.
func LoadSignerVerifierWithOpts(privateKey crypto.PrivateKey, opts ...LoadOption) (SignerVerifier, error) {
var rsaPSSOptions *rsa.PSSOptions
var useED25519ph bool
hashFunc := crypto.SHA256
for _, o := range opts {
o.ApplyED25519ph(&useED25519ph)
o.ApplyHash(&hashFunc)
o.ApplyRSAPSS(&rsaPSSOptions)
}
switch pk := privateKey.(type) {
case *rsa.PrivateKey:
if rsaPSSOptions != nil {
return LoadRSAPSSSignerVerifier(pk, hashFunc, rsaPSSOptions)
}
return LoadRSAPKCS1v15SignerVerifier(pk, hashFunc)
case *ecdsa.PrivateKey:
return LoadECDSASignerVerifier(pk, hashFunc)
case ed25519.PrivateKey:
if useED25519ph {
return LoadED25519phSignerVerifier(pk)
}
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 := os.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)
}
// LoadSignerVerifierFromPEMFileWithOpts returns a signature.SignerVerifier based on the algorithm of the private key
// in the file. The SignerVerifier will use the hash function specified in the options when computing digests.
func LoadSignerVerifierFromPEMFileWithOpts(path string, pf cryptoutils.PassFunc, opts ...LoadOption) (SignerVerifier, error) {
fileBytes, err := os.ReadFile(filepath.Clean(path))
if err != nil {
return nil, err
}
priv, err := cryptoutils.UnmarshalPEMToPrivateKey(fileBytes, pf)
if err != nil {
return nil, err
}
return LoadSignerVerifierWithOpts(priv, opts...)
}
// LoadDefaultSignerVerifier returns a signature.SignerVerifier based on
// the private key. Each private key has a corresponding PublicKeyDetails
// associated in the Sigstore ecosystem, see Algorithm Registry for more details.
func LoadDefaultSignerVerifier(privateKey crypto.PrivateKey, opts ...LoadOption) (SignerVerifier, error) {
signer, ok := privateKey.(crypto.Signer)
if !ok {
return nil, errors.New("private key does not implement signature.Signer")
}
algorithmDetails, err := GetDefaultAlgorithmDetails(signer.Public(), opts...)
if err != nil {
return nil, err
}
return LoadSignerVerifierFromAlgorithmDetails(privateKey, algorithmDetails, opts...)
}
// LoadSignerVerifierFromAlgorithmDetails returns a signature.SignerVerifier based on
// the algorithm details and the user's choice of options.
func LoadSignerVerifierFromAlgorithmDetails(privateKey crypto.PrivateKey, algorithmDetails AlgorithmDetails, opts ...LoadOption) (SignerVerifier, error) {
filteredOpts := GetOptsFromAlgorithmDetails(algorithmDetails, opts...)
return LoadSignerVerifierWithOpts(privateKey, filteredOpts...)
}
//
// 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/rsa"
"encoding/json"
"fmt"
"github.com/google/go-containerregistry/pkg/name"
"github.com/sigstore/sigstore/pkg/signature/options"
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
}
// GetOptsFromAlgorithmDetails returns a list of LoadOptions that are
// appropriate for the given algorithm details. It ignores the hash type because
// that can be retrieved from the algorithm details.
func GetOptsFromAlgorithmDetails(algorithmDetails AlgorithmDetails, opts ...LoadOption) []LoadOption {
res := []LoadOption{options.WithHash(algorithmDetails.hashType)}
for _, opt := range opts {
var useED25519ph bool
var rsaPSSOptions *rsa.PSSOptions
opt.ApplyED25519ph(&useED25519ph)
opt.ApplyRSAPSS(&rsaPSSOptions)
if useED25519ph || rsaPSSOptions != nil {
res = append(res, opt)
}
}
return res
}
//
// 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"
"os"
"path/filepath"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature/options"
)
// 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) {
return LoadVerifierWithOpts(publicKey, options.WithHash(hashFunc))
}
// LoadVerifierWithOpts returns a signature.Verifier based on the algorithm of the public key
// provided that will use the hash function specified when computing digests.
func LoadVerifierWithOpts(publicKey crypto.PublicKey, opts ...LoadOption) (Verifier, error) {
var rsaPSSOptions *rsa.PSSOptions
var useED25519ph bool
hashFunc := crypto.SHA256
for _, o := range opts {
o.ApplyED25519ph(&useED25519ph)
o.ApplyHash(&hashFunc)
o.ApplyRSAPSS(&rsaPSSOptions)
}
switch pk := publicKey.(type) {
case *rsa.PublicKey:
if rsaPSSOptions != nil {
return LoadRSAPSSVerifier(pk, hashFunc, rsaPSSOptions)
}
return LoadRSAPKCS1v15Verifier(pk, hashFunc)
case *ecdsa.PublicKey:
return LoadECDSAVerifier(pk, hashFunc)
case ed25519.PublicKey:
if useED25519ph {
return LoadED25519phVerifier(pk)
}
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 := os.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)
}
// LoadVerifierFromPEMFileWithOpts returns a signature.Verifier based on the contents of a
// file located at path. The Verifier wil use the hash function specified in the options when computing digests.
func LoadVerifierFromPEMFileWithOpts(path string, opts ...LoadOption) (Verifier, error) {
fileBytes, err := os.ReadFile(filepath.Clean(path))
if err != nil {
return nil, err
}
pubKey, err := cryptoutils.UnmarshalPEMToPublicKey(fileBytes)
if err != nil {
return nil, err
}
return LoadVerifierWithOpts(pubKey, opts...)
}
// LoadDefaultVerifier returns a signature.Verifier based on the public key.
// Each public key has a corresponding PublicKeyDetails associated in the
// Sigstore ecosystem, see Algorithm Registry for more details.
func LoadDefaultVerifier(publicKey crypto.PublicKey, opts ...LoadOption) (Verifier, error) {
algorithmDetails, err := GetDefaultAlgorithmDetails(publicKey, opts...)
if err != nil {
return nil, err
}
return LoadVerifierFromAlgorithmDetails(publicKey, algorithmDetails, opts...)
}
// LoadVerifierFromAlgorithmDetails returns a signature.Verifier based on
// the algorithm details and the user's choice of options.
func LoadVerifierFromAlgorithmDetails(publicKey crypto.PublicKey, algorithmDetails AlgorithmDetails, opts ...LoadOption) (Verifier, error) {
filteredOpts := GetOptsFromAlgorithmDetails(algorithmDetails, opts...)
return LoadVerifierWithOpts(publicKey, filteredOpts...)
}