go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/tokenserver/client/x509signer.go (about)

     1  // Copyright 2016 The LUCI Authors.
     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 client
    16  
    17  import (
    18  	"context"
    19  	"crypto"
    20  	"crypto/rsa"
    21  	"crypto/x509"
    22  	"encoding/pem"
    23  	"fmt"
    24  	"os"
    25  	"sync"
    26  
    27  	"go.chromium.org/luci/common/data/rand/cryptorand"
    28  )
    29  
    30  // TODO(vadimsh): Adding support for ECDSA should be trivial if needed.
    31  
    32  // X509Signer implements Signer interface by using a private key and
    33  // a certificate specified in ANS.1 x509 PEM encoded structures.
    34  //
    35  // It is fine to initialize this struct directly if you have loaded private key
    36  // and certificate already. You can optionally use Validate() to make sure they
    37  // are valid before making other calls (all calls do validation anyhow).
    38  //
    39  // Use LoadX509Signer to load the key and certificate from files on disk.
    40  type X509Signer struct {
    41  	// PrivateKeyPEM is PEM-encoded ASN.1 PKCS#1 private key.
    42  	//
    43  	// See https://openssl.org/docs/manmaster/apps/rsa.html.
    44  	PrivateKeyPEM []byte
    45  
    46  	// CertificatePEM is PEM-encoded ASN.1 x509 certificate.
    47  	//
    48  	// It must contain a public key matching the private key specified by
    49  	// PrivateKeyPEM (this will be verified).
    50  	//
    51  	// See https://openssl.org/docs/manmaster/apps/x509.html.
    52  	CertificatePEM []byte
    53  
    54  	// Fields below are lazy initialized on first use in Validate().
    55  
    56  	init    sync.Once
    57  	err     error
    58  	algo    x509.SignatureAlgorithm
    59  	certDer []byte
    60  	pkey    crypto.Signer
    61  }
    62  
    63  // LoadX509Signer parses and validates private key and certificate PEM files.
    64  //
    65  // Returns X509Signer that is ready for work.
    66  func LoadX509Signer(privateKeyPath, certPath string) (*X509Signer, error) {
    67  	pkey, err := os.ReadFile(privateKeyPath)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	cert, err := os.ReadFile(certPath)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	signer := &X509Signer{
    76  		PrivateKeyPEM:  pkey,
    77  		CertificatePEM: cert,
    78  	}
    79  	if err = signer.Validate(); err != nil {
    80  		return nil, err
    81  	}
    82  	return signer, nil
    83  }
    84  
    85  // Algo returns an algorithm that the signer implements.
    86  func (s *X509Signer) Algo(ctx context.Context) (x509.SignatureAlgorithm, error) {
    87  	if err := s.Validate(); err != nil {
    88  		return 0, err
    89  	}
    90  	return s.algo, nil
    91  }
    92  
    93  // Certificate returns ASN.1 DER blob with the certificate of the signer.
    94  func (s *X509Signer) Certificate(ctx context.Context) ([]byte, error) {
    95  	if err := s.Validate(); err != nil {
    96  		return nil, err
    97  	}
    98  	return s.certDer, nil
    99  }
   100  
   101  // Sign signs a blob using the private key.
   102  func (s *X509Signer) Sign(ctx context.Context, blob []byte) ([]byte, error) {
   103  	if err := s.Validate(); err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	var hashFunc crypto.Hash
   108  	switch s.algo {
   109  	case x509.SHA256WithRSA:
   110  		hashFunc = crypto.SHA256
   111  	default:
   112  		panic("someone forgot to implement hashing algo for new kind of a key")
   113  	}
   114  
   115  	h := hashFunc.New()
   116  	h.Write(blob)
   117  	digest := h.Sum(nil)
   118  
   119  	return s.pkey.Sign(cryptorand.Get(ctx), digest, hashFunc)
   120  }
   121  
   122  // Validate parses the private key and certificate file and verifies them.
   123  //
   124  // It checks that the public portion of the key matches what's in the
   125  // certificate.
   126  func (s *X509Signer) Validate() error {
   127  	s.init.Do(func() {
   128  		s.err = s.initialize()
   129  	})
   130  	return s.err
   131  }
   132  
   133  func (s *X509Signer) initialize() error {
   134  	pkey, err := parsePrivateKey(s.PrivateKeyPEM)
   135  	if err != nil {
   136  		return err
   137  	}
   138  	cert, certDer, err := parseCertificate(s.CertificatePEM)
   139  	if err != nil {
   140  		return err
   141  	}
   142  
   143  	// Make sure the private key matches the public key in the cert. Also pick
   144  	// the corresponding signing algorithm based on the type of the key.
   145  	var algo x509.SignatureAlgorithm
   146  	switch key := pkey.(type) {
   147  	case *rsa.PrivateKey:
   148  		var pub *rsa.PublicKey
   149  		if pub, _ = cert.PublicKey.(*rsa.PublicKey); pub == nil {
   150  			return fmt.Errorf("the certificate doesn't match the private key - wrong types")
   151  		}
   152  		if key.PublicKey.E != pub.E || key.PublicKey.N.Cmp(pub.N) != 0 {
   153  			return fmt.Errorf("the certificate doesn't match the private key")
   154  		}
   155  		algo = x509.SHA256WithRSA
   156  	default:
   157  		panic("someone forgot to implement public key check for new kind of a key")
   158  	}
   159  
   160  	s.algo = algo
   161  	s.certDer = certDer
   162  	s.pkey = pkey
   163  	return nil
   164  }
   165  
   166  func parsePrivateKey(pemData []byte) (crypto.Signer, error) {
   167  	block, rest := pem.Decode(pemData)
   168  	if len(rest) != 0 || block == nil {
   169  		return nil, fmt.Errorf("not a valid private key PEM")
   170  	}
   171  	switch block.Type {
   172  	case "RSA PRIVATE KEY":
   173  		return x509.ParsePKCS1PrivateKey(block.Bytes)
   174  	default:
   175  		return nil, fmt.Errorf("not a supported private key type - %q", block.Type)
   176  	}
   177  }
   178  
   179  func parseCertificate(pemData []byte) (*x509.Certificate, []byte, error) {
   180  	block, rest := pem.Decode(pemData)
   181  	if len(rest) != 0 || block == nil {
   182  		return nil, nil, fmt.Errorf("not a valid certificate PEM")
   183  	}
   184  	if block.Type != "CERTIFICATE" {
   185  		return nil, nil, fmt.Errorf("expecting \"CERTIFICATE\" PEM, got %q", block.Type)
   186  	}
   187  	cert, err := x509.ParseCertificate(block.Bytes)
   188  	if err != nil {
   189  		return nil, nil, err
   190  	}
   191  	return cert, block.Bytes, err
   192  }