github.com/google/osv-scalibr@v0.4.1/veles/secrets/gcpsak/signature.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package gcpsak
    16  
    17  import (
    18  	"crypto"
    19  	"crypto/rsa"
    20  	"crypto/x509"
    21  	"encoding/pem"
    22  	"errors"
    23  	"fmt"
    24  )
    25  
    26  const (
    27  	// hashAlgo is the cryptographic hash function used to hash the payload before
    28  	// signing it.
    29  	// We chose SHA256, because that is what's used in the JWT / OAuth2 flow that
    30  	// GCP SAK are ultimately for.
    31  	// See https://cs.opensource.google/go/x/oauth2/+/refs/tags/v0.20.0:jws/jws.go;l=156 for details.
    32  	hashAlgo = crypto.SHA256
    33  
    34  	// payload is used for every signature.
    35  	// We chose a known static payload, so we don't accidentally generate
    36  	// something that could be used for a real authentication flow on an attack on
    37  	// it.
    38  	payload = "Don't leak keys, pretty please!"
    39  )
    40  
    41  var (
    42  	// payloadHash contains the (constant) hash of the payload.
    43  	// Since we're using a static payload, we can compute its hash ahead of time.
    44  	payloadHash []byte
    45  )
    46  
    47  func init() { //nolint:gochecknoinits
    48  	h := hashAlgo.New()
    49  	if _, err := h.Write([]byte(payload)); err != nil {
    50  		// Guaranteed to never return an error.
    51  		panic(err)
    52  	}
    53  	payloadHash = h.Sum(nil)
    54  }
    55  
    56  // Sign uses the privateKey (PEM format) to sign the static payload.
    57  // This allows us to validate a GCP SAK downstream without having to hold on to
    58  // (and thus potentially leak) its private key.
    59  //
    60  // It uses the same flow that the JWT / OAuth2 flow uses for GCP SAK, so we
    61  // don't accidentally expose the key to cross-algorithm attacks. It uses SHA256
    62  // and SignPKCS1v15.
    63  // For details, see https://cs.opensource.google/go/x/oauth2/+/refs/tags/v0.20.0:jws/jws.go;l=156.
    64  //
    65  // Returns nil if signing was not possible; i.e. because the key was not
    66  // well-formed.
    67  func Sign(privateKey string) []byte {
    68  	block, _ := pem.Decode([]byte(privateKey))
    69  	if block == nil || block.Type != "PRIVATE KEY" {
    70  		return nil
    71  	}
    72  	key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
    73  	if err != nil {
    74  		return nil
    75  	}
    76  	priv, ok := key.(*rsa.PrivateKey)
    77  	if !ok {
    78  		return nil
    79  	}
    80  	sig, err := rsa.SignPKCS1v15(nil, priv, hashAlgo, payloadHash)
    81  	if err != nil {
    82  		return nil
    83  	}
    84  	return sig
    85  }
    86  
    87  // Valid checks whether sig was signed using the private key contained in the
    88  // certificate cert (PEM format).
    89  //
    90  // Returns true if the signature was successfully validated; false otherwise.
    91  // Returns error if parsing the certificate or extracting the public key failed.
    92  func Valid(sig []byte, cert string) (bool, error) {
    93  	block, _ := pem.Decode([]byte(cert))
    94  	if block == nil || block.Type != "CERTIFICATE" {
    95  		return false, errors.New("no valid PEM block of type CERTIFICATE")
    96  	}
    97  	crt, err := x509.ParseCertificate(block.Bytes)
    98  	if err != nil {
    99  		return false, fmt.Errorf("unable to parse certificate: %w", err)
   100  	}
   101  	pub, ok := crt.PublicKey.(*rsa.PublicKey)
   102  	if !ok {
   103  		return false, fmt.Errorf("public key was %T not RSA public key", crt.PublicKey)
   104  	}
   105  	if err := rsa.VerifyPKCS1v15(pub, hashAlgo, payloadHash, sig); err != nil {
   106  		return false, nil //nolint:nilerr
   107  	}
   108  	return true, nil
   109  }