// Copyright 2018 The OPA Authors. All rights reserved. // Use of this source code is governed by an Apache2 // license that can be found in the LICENSE file. package topdown import ( "bytes" "crypto" "crypto/hmac" "crypto/md5" "crypto/sha1" "crypto/sha256" "crypto/sha512" "crypto/tls" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "fmt" "hash" "os" "strings" "github.com/open-policy-agent/opa/internal/jwx/jwk" "github.com/open-policy-agent/opa/ast" "github.com/open-policy-agent/opa/topdown/builtins" "github.com/open-policy-agent/opa/util" ) const ( // blockTypeCertificate indicates this PEM block contains the signed certificate. // Exported for tests. blockTypeCertificate = "CERTIFICATE" // blockTypeCertificateRequest indicates this PEM block contains a certificate // request. Exported for tests. blockTypeCertificateRequest = "CERTIFICATE REQUEST" // blockTypeRSAPrivateKey indicates this PEM block contains a RSA private key. // Exported for tests. blockTypeRSAPrivateKey = "RSA PRIVATE KEY" // blockTypeRSAPrivateKey indicates this PEM block contains a RSA private key. // Exported for tests. blockTypePrivateKey = "PRIVATE KEY" blockTypeEcPrivateKey = "EC PRIVATE KEY" ) func builtinCryptoX509ParseCertificates(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { input, err := builtins.StringOperand(operands[0].Value, 1) if err != nil { return err } certs, err := getX509CertsFromString(string(input)) if err != nil { return err } v, err := ast.InterfaceToValue(extendCertificates(certs)) if err != nil { return err } return iter(ast.NewTerm(v)) } // extendedCert is a wrapper around x509.Certificate that adds additional fields for JSON serialization. type extendedCert struct { x509.Certificate URIStrings []string } func extendCertificates(certs []*x509.Certificate) []extendedCert { // add a field to certs containing the URIs as strings processedCerts := make([]extendedCert, len(certs)) for i, cert := range certs { processedCerts[i].Certificate = *cert if cert.URIs != nil { processedCerts[i].URIStrings = make([]string, len(cert.URIs)) for j, uri := range cert.URIs { processedCerts[i].URIStrings[j] = uri.String() } } } return processedCerts } func builtinCryptoX509ParseAndVerifyCertificates(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { a := operands[0].Value input, err := builtins.StringOperand(a, 1) if err != nil { return err } invalid := ast.ArrayTerm( ast.BooleanTerm(false), ast.NewTerm(ast.NewArray()), ) certs, err := getX509CertsFromString(string(input)) if err != nil { return iter(invalid) } verified, err := verifyX509CertificateChain(certs) if err != nil { return iter(invalid) } value, err := ast.InterfaceToValue(extendCertificates(verified)) if err != nil { return err } valid := ast.ArrayTerm( ast.BooleanTerm(true), ast.NewTerm(value), ) return iter(valid) } func builtinCryptoX509ParseKeyPair(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { certificate, err := builtins.StringOperand(operands[0].Value, 1) if err != nil { return err } key, err := builtins.StringOperand(operands[1].Value, 1) if err != nil { return err } certs, err := getTLSx509KeyPairFromString([]byte(certificate), []byte(key)) if err != nil { return err } v, err := ast.InterfaceToValue(certs) if err != nil { return err } return iter(ast.NewTerm(v)) } func builtinCryptoX509ParseCertificateRequest(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { input, err := builtins.StringOperand(operands[0].Value, 1) if err != nil { return err } // data to be passed to x509.ParseCertificateRequest bytes := []byte(input) // if the input is not a PEM string, attempt to decode b64 if str := string(input); !strings.HasPrefix(str, "-----BEGIN CERTIFICATE REQUEST-----") { bytes, err = base64.StdEncoding.DecodeString(str) if err != nil { return err } } p, _ := pem.Decode(bytes) if p != nil && p.Type != blockTypeCertificateRequest { return fmt.Errorf("invalid PEM-encoded certificate signing request") } if p != nil { bytes = p.Bytes } csr, err := x509.ParseCertificateRequest(bytes) if err != nil { return err } bs, err := json.Marshal(csr) if err != nil { return err } var x interface{} if err := util.UnmarshalJSON(bs, &x); err != nil { return err } v, err := ast.InterfaceToValue(x) if err != nil { return err } return iter(ast.NewTerm(v)) } func builtinCryptoJWKFromPrivateKey(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { var x interface{} a := operands[0].Value input, err := builtins.StringOperand(a, 1) if err != nil { return err } // get the raw private key pemDataString := string(input) if pemDataString == "" { return fmt.Errorf("input PEM data was empty") } // This built in must be supplied a valid PEM or base64 encoded string. // If the input is not a PEM string, attempt to decode b64. // If the base64 decode fails - this is an error if !strings.HasPrefix(pemDataString, "-----BEGIN") { bs, err := base64.StdEncoding.DecodeString(pemDataString) if err != nil { return err } pemDataString = string(bs) } rawKeys, err := getPrivateKeysFromPEMData(pemDataString) if err != nil { return err } if len(rawKeys) == 0 { return iter(ast.NullTerm()) } key, err := jwk.New(rawKeys[0]) if err != nil { return err } jsonKey, err := json.Marshal(key) if err != nil { return err } if err := util.UnmarshalJSON(jsonKey, &x); err != nil { return err } value, err := ast.InterfaceToValue(x) if err != nil { return err } return iter(ast.NewTerm(value)) } func builtinCryptoParsePrivateKeys(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { a := operands[0].Value input, err := builtins.StringOperand(a, 1) if err != nil { return err } if string(input) == "" { return iter(ast.NullTerm()) } // get the raw private key rawKeys, err := getPrivateKeysFromPEMData(string(input)) if err != nil { return err } if len(rawKeys) == 0 { return iter(ast.NewTerm(ast.NewArray())) } bs, err := json.Marshal(rawKeys) if err != nil { return err } var x interface{} if err := util.UnmarshalJSON(bs, &x); err != nil { return err } value, err := ast.InterfaceToValue(x) if err != nil { return err } return iter(ast.NewTerm(value)) } func hashHelper(a ast.Value, h func(ast.String) string) (ast.Value, error) { s, err := builtins.StringOperand(a, 1) if err != nil { return nil, err } return ast.String(h(s)), nil } func builtinCryptoMd5(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { res, err := hashHelper(operands[0].Value, func(s ast.String) string { return fmt.Sprintf("%x", md5.Sum([]byte(s))) }) if err != nil { return err } return iter(ast.NewTerm(res)) } func builtinCryptoSha1(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { res, err := hashHelper(operands[0].Value, func(s ast.String) string { return fmt.Sprintf("%x", sha1.Sum([]byte(s))) }) if err != nil { return err } return iter(ast.NewTerm(res)) } func builtinCryptoSha256(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { res, err := hashHelper(operands[0].Value, func(s ast.String) string { return fmt.Sprintf("%x", sha256.Sum256([]byte(s))) }) if err != nil { return err } return iter(ast.NewTerm(res)) } func hmacHelper(operands []*ast.Term, iter func(*ast.Term) error, h func() hash.Hash) error { a1 := operands[0].Value message, err := builtins.StringOperand(a1, 1) if err != nil { return err } a2 := operands[1].Value key, err := builtins.StringOperand(a2, 2) if err != nil { return err } mac := hmac.New(h, []byte(key)) mac.Write([]byte(message)) messageDigest := mac.Sum(nil) return iter(ast.StringTerm(fmt.Sprintf("%x", messageDigest))) } func builtinCryptoHmacMd5(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { return hmacHelper(operands, iter, md5.New) } func builtinCryptoHmacSha1(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { return hmacHelper(operands, iter, sha1.New) } func builtinCryptoHmacSha256(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { return hmacHelper(operands, iter, sha256.New) } func builtinCryptoHmacSha512(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { return hmacHelper(operands, iter, sha512.New) } func builtinCryptoHmacEqual(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { a1 := operands[0].Value mac1, err := builtins.StringOperand(a1, 1) if err != nil { return err } a2 := operands[1].Value mac2, err := builtins.StringOperand(a2, 2) if err != nil { return err } res := hmac.Equal([]byte(mac1), []byte(mac2)) return iter(ast.BooleanTerm(res)) } func init() { RegisterBuiltinFunc(ast.CryptoX509ParseCertificates.Name, builtinCryptoX509ParseCertificates) RegisterBuiltinFunc(ast.CryptoX509ParseAndVerifyCertificates.Name, builtinCryptoX509ParseAndVerifyCertificates) RegisterBuiltinFunc(ast.CryptoMd5.Name, builtinCryptoMd5) RegisterBuiltinFunc(ast.CryptoSha1.Name, builtinCryptoSha1) RegisterBuiltinFunc(ast.CryptoSha256.Name, builtinCryptoSha256) RegisterBuiltinFunc(ast.CryptoX509ParseCertificateRequest.Name, builtinCryptoX509ParseCertificateRequest) RegisterBuiltinFunc(ast.CryptoX509ParseRSAPrivateKey.Name, builtinCryptoJWKFromPrivateKey) RegisterBuiltinFunc(ast.CryptoParsePrivateKeys.Name, builtinCryptoParsePrivateKeys) RegisterBuiltinFunc(ast.CryptoX509ParseKeyPair.Name, builtinCryptoX509ParseKeyPair) RegisterBuiltinFunc(ast.CryptoHmacMd5.Name, builtinCryptoHmacMd5) RegisterBuiltinFunc(ast.CryptoHmacSha1.Name, builtinCryptoHmacSha1) RegisterBuiltinFunc(ast.CryptoHmacSha256.Name, builtinCryptoHmacSha256) RegisterBuiltinFunc(ast.CryptoHmacSha512.Name, builtinCryptoHmacSha512) RegisterBuiltinFunc(ast.CryptoHmacEqual.Name, builtinCryptoHmacEqual) } func verifyX509CertificateChain(certs []*x509.Certificate) ([]*x509.Certificate, error) { if len(certs) < 2 { return nil, builtins.NewOperandErr(1, "must supply at least two certificates to be able to verify") } // first cert is the root roots := x509.NewCertPool() roots.AddCert(certs[0]) // all other certs except the last are intermediates intermediates := x509.NewCertPool() for i := 1; i < len(certs)-1; i++ { intermediates.AddCert(certs[i]) } // last cert is the leaf leaf := certs[len(certs)-1] // verify the cert chain back to the root verifyOpts := x509.VerifyOptions{ Roots: roots, Intermediates: intermediates, } chains, err := leaf.Verify(verifyOpts) if err != nil { return nil, err } return chains[0], nil } func getX509CertsFromString(certs string) ([]*x509.Certificate, error) { // if the input is PEM handle that if strings.HasPrefix(certs, "-----BEGIN") { return getX509CertsFromPem([]byte(certs)) } // assume input is base64 if not PEM b64, err := base64.StdEncoding.DecodeString(certs) if err != nil { return nil, err } // handle if the decoded base64 contains PEM rather than the expected DER if bytes.HasPrefix(b64, []byte("-----BEGIN")) { return getX509CertsFromPem(b64) } // otherwise assume the contents are DER return x509.ParseCertificates(b64) } func getX509CertsFromPem(pemBlocks []byte) ([]*x509.Certificate, error) { var decodedCerts []byte for len(pemBlocks) > 0 { p, r := pem.Decode(pemBlocks) if p != nil && p.Type != blockTypeCertificate { return nil, fmt.Errorf("PEM block type is '%s', expected %s", p.Type, blockTypeCertificate) } if p == nil { break } pemBlocks = r decodedCerts = append(decodedCerts, p.Bytes...) } return x509.ParseCertificates(decodedCerts) } func getPrivateKeysFromPEMData(pemData string) ([]crypto.PrivateKey, error) { pemBlockString := pemData var validPrivateKeys []crypto.PrivateKey // if the input is base64, decode it bs, err := base64.StdEncoding.DecodeString(pemBlockString) if err == nil { pemBlockString = string(bs) } bs = []byte(pemBlockString) for len(bs) > 0 { inputLen := len(bs) var block *pem.Block block, bs = pem.Decode(bs) if block == nil && len(bs) == 0 { break } // should only happen if end of input is not a valid PEM block. See TestParseRSAPrivateKeyVariedPemInput. if inputLen == len(bs) { break } if block == nil { continue } switch block.Type { case blockTypeRSAPrivateKey: parsedKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return nil, err } validPrivateKeys = append(validPrivateKeys, parsedKey) case blockTypePrivateKey: parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { return nil, err } validPrivateKeys = append(validPrivateKeys, parsedKey) case blockTypeEcPrivateKey: parsedKey, err := x509.ParseECPrivateKey(block.Bytes) if err != nil { return nil, err } validPrivateKeys = append(validPrivateKeys, parsedKey) } } return validPrivateKeys, nil } // addCACertsFromFile adds CA certificates from filePath into the given pool. // If pool is nil, it creates a new x509.CertPool. pool is returned. func addCACertsFromFile(pool *x509.CertPool, filePath string) (*x509.CertPool, error) { if pool == nil { pool = x509.NewCertPool() } caCert, err := readCertFromFile(filePath) if err != nil { return nil, err } if ok := pool.AppendCertsFromPEM(caCert); !ok { return nil, fmt.Errorf("could not append CA certificates from %q", filePath) } return pool, nil } // addCACertsFromBytes adds CA certificates from pemBytes into the given pool. // If pool is nil, it creates a new x509.CertPool. pool is returned. func addCACertsFromBytes(pool *x509.CertPool, pemBytes []byte) (*x509.CertPool, error) { if pool == nil { pool = x509.NewCertPool() } if ok := pool.AppendCertsFromPEM(pemBytes); !ok { return nil, fmt.Errorf("could not append certificates") } return pool, nil } // addCACertsFromEnv adds CA certificates from the environment variable named // by envName into the given pool. If pool is nil, it creates a new x509.CertPool. // pool is returned. func addCACertsFromEnv(pool *x509.CertPool, envName string) (*x509.CertPool, error) { pool, err := addCACertsFromBytes(pool, []byte(os.Getenv(envName))) if err != nil { return nil, fmt.Errorf("could not add CA certificates from envvar %q: %w", envName, err) } return pool, err } // ReadCertFromFile reads a cert from file func readCertFromFile(localCertFile string) ([]byte, error) { // Read in the cert file certPEM, err := os.ReadFile(localCertFile) if err != nil { return nil, err } return certPEM, nil } func getTLSx509KeyPairFromString(certPemBlock []byte, keyPemBlock []byte) (*tls.Certificate, error) { if !strings.HasPrefix(string(certPemBlock), "-----BEGIN") { s, err := base64.StdEncoding.DecodeString(string(certPemBlock)) if err != nil { return nil, err } certPemBlock = s } if !strings.HasPrefix(string(keyPemBlock), "-----BEGIN") { s, err := base64.StdEncoding.DecodeString(string(keyPemBlock)) if err != nil { return nil, err } keyPemBlock = s } // we assume it a DER certificate and try to convert it to a PEM. if !bytes.HasPrefix(certPemBlock, []byte("-----BEGIN")) { pemBlock := &pem.Block{ Type: "CERTIFICATE", Bytes: certPemBlock, } var buf bytes.Buffer if err := pem.Encode(&buf, pemBlock); err != nil { return nil, err } certPemBlock = buf.Bytes() } // we assume it a DER key and try to convert it to a PEM. if !bytes.HasPrefix(keyPemBlock, []byte("-----BEGIN")) { pemBlock := &pem.Block{ Type: "PRIVATE KEY", Bytes: keyPemBlock, } var buf bytes.Buffer if err := pem.Encode(&buf, pemBlock); err != nil { return nil, err } keyPemBlock = buf.Bytes() } cert, err := tls.X509KeyPair(certPemBlock, keyPemBlock) if err != nil { return nil, err } return &cert, nil } // ReadKeyFromFile reads a key from file func readKeyFromFile(localKeyFile string) ([]byte, error) { // Read in the cert file key, err := os.ReadFile(localKeyFile) if err != nil { return nil, err } return key, nil }