131 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			131 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
	
| package privatekey
 | |
| 
 | |
| import (
 | |
| 	"crypto"
 | |
| 	"crypto/ecdsa"
 | |
| 	"crypto/rand"
 | |
| 	"crypto/rsa"
 | |
| 	"crypto/sha256"
 | |
| 	"crypto/x509"
 | |
| 	"encoding/pem"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"hash"
 | |
| 	"os"
 | |
| )
 | |
| 
 | |
| func makeVerifyHash() (hash.Hash, error) {
 | |
| 	randBytes := make([]byte, 32)
 | |
| 	_, err := rand.Read(randBytes)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	hash := sha256.New()
 | |
| 	_, err = hash.Write(randBytes)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return hash, nil
 | |
| }
 | |
| 
 | |
| // verifyRSA is broken out of Verify for testing purposes.
 | |
| func verifyRSA(privKey *rsa.PrivateKey, pubKey *rsa.PublicKey, msgHash hash.Hash) (crypto.Signer, crypto.PublicKey, error) {
 | |
| 	signatureRSA, err := rsa.SignPSS(rand.Reader, privKey, crypto.SHA256, msgHash.Sum(nil), nil)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, fmt.Errorf("failed to sign using the provided RSA private key: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	err = rsa.VerifyPSS(pubKey, crypto.SHA256, msgHash.Sum(nil), signatureRSA, nil)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, fmt.Errorf("the provided RSA private key failed signature verification: %s", err)
 | |
| 	}
 | |
| 	return privKey, privKey.Public(), nil
 | |
| }
 | |
| 
 | |
| // verifyECDSA is broken out of Verify for testing purposes.
 | |
| func verifyECDSA(privKey *ecdsa.PrivateKey, pubKey *ecdsa.PublicKey, msgHash hash.Hash) (crypto.Signer, crypto.PublicKey, error) {
 | |
| 	r, s, err := ecdsa.Sign(rand.Reader, privKey, msgHash.Sum(nil))
 | |
| 	if err != nil {
 | |
| 		return nil, nil, fmt.Errorf("failed to sign using the provided ECDSA private key: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	verify := ecdsa.Verify(pubKey, msgHash.Sum(nil), r, s)
 | |
| 	if !verify {
 | |
| 		return nil, nil, errors.New("the provided ECDSA private key failed signature verification")
 | |
| 	}
 | |
| 	return privKey, privKey.Public(), nil
 | |
| }
 | |
| 
 | |
| // verify ensures that the embedded PublicKey of the provided privateKey is
 | |
| // actually a match for the private key. For an example of private keys
 | |
| // embedding a mismatched public key, see:
 | |
| // https://blog.hboeck.de/archives/888-How-I-tricked-Symantec-with-a-Fake-Private-Key.html.
 | |
| func verify(privateKey crypto.Signer) (crypto.Signer, crypto.PublicKey, error) {
 | |
| 	verifyHash, err := makeVerifyHash()
 | |
| 	if err != nil {
 | |
| 		return nil, nil, err
 | |
| 	}
 | |
| 
 | |
| 	switch k := privateKey.(type) {
 | |
| 	case *rsa.PrivateKey:
 | |
| 		return verifyRSA(k, &k.PublicKey, verifyHash)
 | |
| 
 | |
| 	case *ecdsa.PrivateKey:
 | |
| 		return verifyECDSA(k, &k.PublicKey, verifyHash)
 | |
| 
 | |
| 	default:
 | |
| 		// This should never happen.
 | |
| 		return nil, nil, errors.New("the provided private key could not be asserted to ECDSA or RSA")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Load decodes and parses a private key from the provided file path and returns
 | |
| // the private key as crypto.Signer. keyPath is expected to be a PEM formatted
 | |
| // RSA or ECDSA private key in a PKCS #1, PKCS# 8, or SEC 1 container. The
 | |
| // embedded PublicKey of the provided private key will be verified as an actual
 | |
| // match for the private key and returned as a crypto.PublicKey. This function
 | |
| // is only intended for use in administrative tooling and tests.
 | |
| func Load(keyPath string) (crypto.Signer, crypto.PublicKey, error) {
 | |
| 	keyBytes, err := os.ReadFile(keyPath)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, fmt.Errorf("could not read key file %q", keyPath)
 | |
| 	}
 | |
| 
 | |
| 	var keyDER *pem.Block
 | |
| 	for {
 | |
| 		keyDER, keyBytes = pem.Decode(keyBytes)
 | |
| 		if keyDER == nil || keyDER.Type != "EC PARAMETERS" {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	if keyDER == nil {
 | |
| 		return nil, nil, fmt.Errorf("no PEM formatted block found in %q", keyPath)
 | |
| 	}
 | |
| 
 | |
| 	// Attempt to parse the PEM block as a private key in a PKCS #8 container.
 | |
| 	signer, err := x509.ParsePKCS8PrivateKey(keyDER.Bytes)
 | |
| 	if err == nil {
 | |
| 		cryptoSigner, ok := signer.(crypto.Signer)
 | |
| 		if ok {
 | |
| 			return verify(cryptoSigner)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Attempt to parse the PEM block as a private key in a PKCS #1 container.
 | |
| 	rsaSigner, err := x509.ParsePKCS1PrivateKey(keyDER.Bytes)
 | |
| 	if err != nil && keyDER.Type == "RSA PRIVATE KEY" {
 | |
| 		return nil, nil, fmt.Errorf("unable to parse %q as a PKCS#1 RSA private key: %w", keyPath, err)
 | |
| 	}
 | |
| 	if err == nil {
 | |
| 		return verify(rsaSigner)
 | |
| 	}
 | |
| 
 | |
| 	// Attempt to parse the PEM block as a private key in a SEC 1 container.
 | |
| 	ecdsaSigner, err := x509.ParseECPrivateKey(keyDER.Bytes)
 | |
| 	if err == nil {
 | |
| 		return verify(ecdsaSigner)
 | |
| 	}
 | |
| 	return nil, nil, fmt.Errorf("unable to parse %q as a private key", keyPath)
 | |
| }
 |