istio.io/istio@v0.0.0-20240520182934-d79c90f27776/security/pkg/pki/util/crypto.go (about)

     1  // Copyright Istio 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 util
    16  
    17  import (
    18  	"crypto"
    19  	"crypto/ecdsa"
    20  	"crypto/elliptic"
    21  	"crypto/rsa"
    22  	"crypto/x509"
    23  	"encoding/pem"
    24  	"fmt"
    25  	"reflect"
    26  	"strings"
    27  )
    28  
    29  const (
    30  	blockTypeECPrivateKey    = "EC PRIVATE KEY"
    31  	blockTypeRSAPrivateKey   = "RSA PRIVATE KEY" // PKCS#1 private key
    32  	blockTypePKCS8PrivateKey = "PRIVATE KEY"     // PKCS#8 plain private key
    33  )
    34  
    35  // ParsePemEncodedCertificate constructs a `x509.Certificate` object using the
    36  // given a PEM-encoded certificate.
    37  func ParsePemEncodedCertificate(certBytes []byte) (*x509.Certificate, error) {
    38  	cb, _ := pem.Decode(certBytes)
    39  	if cb == nil {
    40  		return nil, fmt.Errorf("invalid PEM encoded certificate")
    41  	}
    42  
    43  	cert, err := x509.ParseCertificate(cb.Bytes)
    44  	if err != nil {
    45  		return nil, fmt.Errorf("failed to parse X.509 certificate")
    46  	}
    47  
    48  	return cert, nil
    49  }
    50  
    51  // ParsePemEncodedCertificateChain constructs a slice of `x509.Certificate` and `rootCertBytes`
    52  // objects using the given a PEM-encoded certificate chain.
    53  func ParsePemEncodedCertificateChain(certBytes []byte) ([]*x509.Certificate, []byte, error) {
    54  	var (
    55  		certs         []*x509.Certificate
    56  		cb            *pem.Block
    57  		rootCertBytes []byte
    58  	)
    59  	for {
    60  		rootCertBytes = certBytes
    61  		cb, certBytes = pem.Decode(certBytes)
    62  		if cb == nil {
    63  			return nil, nil, fmt.Errorf("invalid PEM encoded certificate")
    64  		}
    65  		cert, err := x509.ParseCertificate(cb.Bytes)
    66  		if err != nil {
    67  			return nil, nil, fmt.Errorf("failed to parse X.509 certificate")
    68  		}
    69  		certs = append(certs, cert)
    70  		if len(certBytes) == 0 {
    71  			break
    72  		}
    73  	}
    74  	if len(certs) == 0 {
    75  		return nil, nil, fmt.Errorf("no PEM encoded X.509 certificates parsed")
    76  	}
    77  	return certs, rootCertBytes, nil
    78  }
    79  
    80  // ParsePemEncodedCSR constructs a `x509.CertificateRequest` object using the
    81  // given PEM-encoded certificate signing request.
    82  func ParsePemEncodedCSR(csrBytes []byte) (*x509.CertificateRequest, error) {
    83  	block, _ := pem.Decode(csrBytes)
    84  	if block == nil {
    85  		return nil, fmt.Errorf("certificate signing request is not properly encoded")
    86  	}
    87  	csr, err := x509.ParseCertificateRequest(block.Bytes)
    88  	if err != nil {
    89  		return nil, fmt.Errorf("failed to parse X.509 certificate signing request")
    90  	}
    91  	return csr, nil
    92  }
    93  
    94  // ParsePemEncodedKey takes a PEM-encoded key and parsed the bytes into a `crypto.PrivateKey`.
    95  func ParsePemEncodedKey(keyBytes []byte) (crypto.PrivateKey, error) {
    96  	kb, _ := pem.Decode(keyBytes)
    97  	if kb == nil {
    98  		return nil, fmt.Errorf("invalid PEM-encoded key")
    99  	}
   100  
   101  	switch kb.Type {
   102  	case blockTypeECPrivateKey:
   103  		key, err := x509.ParseECPrivateKey(kb.Bytes)
   104  		if err != nil {
   105  			return nil, fmt.Errorf("failed to parse the ECDSA private key: %v", err)
   106  		}
   107  		return key, nil
   108  	case blockTypeRSAPrivateKey:
   109  		key, err := x509.ParsePKCS1PrivateKey(kb.Bytes)
   110  		if err != nil {
   111  			return nil, fmt.Errorf("failed to parse the RSA private key: %v", err)
   112  		}
   113  		return key, nil
   114  	case blockTypePKCS8PrivateKey:
   115  		key, err := x509.ParsePKCS8PrivateKey(kb.Bytes)
   116  		if err != nil {
   117  			return nil, fmt.Errorf("failed to parse the PKCS8 private key: %v", err)
   118  		}
   119  		return key, nil
   120  	default:
   121  		return nil, fmt.Errorf("unsupported PEM block type for a private key: %s", kb.Type)
   122  	}
   123  }
   124  
   125  // GetRSAKeySize returns the size if it is RSA key, otherwise it returns an error.
   126  func GetRSAKeySize(privKey crypto.PrivateKey) (int, error) {
   127  	if t := reflect.TypeOf(privKey); t != reflect.TypeOf(&rsa.PrivateKey{}) {
   128  		return 0, fmt.Errorf("key type is not RSA: %v", t)
   129  	}
   130  	pkey := privKey.(*rsa.PrivateKey)
   131  	return pkey.N.BitLen(), nil
   132  }
   133  
   134  // GetEllipticCurve returns the type of curve associated with the private key;
   135  // if ECDSA is used, then only 384 and 256 (default) are returned; if non-ECDSA
   136  // is used then an error is returned
   137  func GetEllipticCurve(privKey *crypto.PrivateKey) (elliptic.Curve, error) {
   138  	switch key := (*privKey).(type) {
   139  	// this should agree with var SupportedECSignatureAlgorithms
   140  	case *ecdsa.PrivateKey:
   141  		if key.Curve == elliptic.P384() {
   142  			return key.Curve, nil
   143  		}
   144  		return elliptic.P256(), nil
   145  	default:
   146  		return nil, fmt.Errorf("private key is not ECDSA based")
   147  	}
   148  }
   149  
   150  // PemCertBytestoString: takes an array of PEM certs in bytes and returns a string array in the same order with
   151  // trailing newline characters removed
   152  func PemCertBytestoString(caCerts []byte) []string {
   153  	certs := []string{}
   154  	var cert string
   155  	pemBlock := caCerts
   156  	for block, rest := pem.Decode(pemBlock); block != nil && len(block.Bytes) != 0; block, rest = pem.Decode(pemBlock) {
   157  		if len(rest) == 0 {
   158  			cert = strings.TrimPrefix(strings.TrimSuffix(string(pemBlock), "\n"), "\n")
   159  			certs = append(certs, cert)
   160  			break
   161  		}
   162  		cert = string(pemBlock[0 : len(pemBlock)-len(rest)])
   163  		cert = strings.TrimPrefix(strings.TrimSuffix(cert, "\n"), "\n")
   164  		certs = append(certs, cert)
   165  		pemBlock = rest
   166  	}
   167  	return certs
   168  }