Skip to content

Commit

Permalink
Allow input as PEM strings to x509 built-ins
Browse files Browse the repository at this point in the history
In the PR discussion here: open-policy-agent#2564 (comment)
we decided that PEM string input was also a valid use case to support.

This change allows string PEM data to be passed as input to both cert
and csr functions.

It also moves the cert and csr functions to be co-located.

Signed-off-by: Charlie Egan <charlieegan3@users.noreply.github.com>
  • Loading branch information
charlieegan3 authored and patrick-east committed Jul 22, 2020
1 parent 061f408 commit 01b6e21
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 102 deletions.
4 changes: 2 additions & 2 deletions docs/content/policy-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -784,8 +784,8 @@ Note that the opa executable will need access to the timezone files in the envir

| Built-in | Description |
| -------- | ----------- |
| <span class="opa-keep-it-together">``output := crypto.x509.parse_certificates(certs)``</span> | ``certs`` is base64 encoded DER or PEM data containing one or more certificates. ``output`` is an array of X.509 certificates represented as JSON objects. |
| <span class="opa-keep-it-together">``output := crypto.x509.parse_certificate_request(csr)``</span> | ``csr`` is a base64 string containing either a PEM encoded or DER CSR.``output`` is an X.509 CSR represented as a JSON object. |
| <span class="opa-keep-it-together">``output := crypto.x509.parse_certificates(certs)``</span> | ``certs`` is base64 encoded DER or PEM data containing one or more certificates or a PEM string of one or more certificates. ``output`` is an array of X.509 certificates represented as JSON objects. |
| <span class="opa-keep-it-together">``output := crypto.x509.parse_certificate_request(csr)``</span> | ``csr`` is a base64 string containing either a PEM encoded or DER CSR or a string containing a PEM CSR.``output`` is an X.509 CSR represented as a JSON object. |
| <span class="opa-keep-it-together">``output := crypto.md5(string)``</span> | ``output`` is ``string`` md5 hashed. |
| <span class="opa-keep-it-together">``output := crypto.sha1(string)``</span> | ``output`` is ``string`` sha1 hashed. |
| <span class="opa-keep-it-together">``output := crypto.sha256(string)``</span> | ``output`` is ``string`` sha256 hashed. |
Expand Down
78 changes: 48 additions & 30 deletions topdown/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,39 @@ import (
"crypto/sha1"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"io/ioutil"
"os"
"strings"

"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/topdown/builtins"
"github.com/open-policy-agent/opa/util"
)

func builtinCryptoX509ParseCertificates(a ast.Value) (ast.Value, error) {
// all data must be base64, regardless of contents
str, err := builtinBase64Decode(a)

input, err := builtins.StringOperand(a, 1)
if err != nil {
return nil, err
}

// default to using []byte from the decoded string as DER data
bytes := []byte(str.(ast.String))
// data to be passed to x509.ParseCertificates
bytes := []byte(input)

// if the input is not a PEM string, attempt to decode b64
if str := string(input); !strings.HasPrefix(str, "-----BEGIN CERTIFICATE-----") {
bytes, err = base64.StdEncoding.DecodeString(str)
if err != nil {
return nil, err
}
}

// attempt to decode input string as PEM data
p, rest := pem.Decode([]byte(str.(ast.String)))
// attempt to decode input as PEM data
p, rest := pem.Decode(bytes)
if p != nil && p.Type != "CERTIFICATE" {
return nil, fmt.Errorf("PEM data contains '%s', expected CERTIFICATE", p.Type)
}
Expand Down Expand Up @@ -76,37 +86,25 @@ func builtinCryptoX509ParseCertificates(a ast.Value) (ast.Value, error) {
return ast.InterfaceToValue(x)
}

func hashHelper(a ast.Value, h func(ast.String) string) (ast.Value, error) {
s, err := builtins.StringOperand(a, 1)
func builtinCryptoX509ParseCertificateRequest(a ast.Value) (ast.Value, error) {

input, err := builtins.StringOperand(a, 1)
if err != nil {
return nil, err
}
return ast.String(h(s)), nil
}

func builtinCryptoMd5(a ast.Value) (ast.Value, error) {
return hashHelper(a, func(s ast.String) string { return fmt.Sprintf("%x", md5.Sum([]byte(s))) })
}

func builtinCryptoSha1(a ast.Value) (ast.Value, error) {
return hashHelper(a, func(s ast.String) string { return fmt.Sprintf("%x", sha1.Sum([]byte(s))) })
}
// data to be passed to x509.ParseCertificateRequest
bytes := []byte(input)

func builtinCryptoSha256(a ast.Value) (ast.Value, error) {
return hashHelper(a, func(s ast.String) string { return fmt.Sprintf("%x", sha256.Sum256([]byte(s))) })
}

func builtinCryptoX509ParseCertificateRequest(a ast.Value) (ast.Value, error) {
// all data must be base64, regardless of contents
str, err := builtinBase64Decode(a)
if err != nil {
return nil, err
// 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 nil, err
}
}

// default to a byte slice from the decoded string
bytes := []byte(str.(ast.String))

p, _ := pem.Decode([]byte(str.(ast.String)))
p, _ := pem.Decode(bytes)
if p != nil && p.Type != "CERTIFICATE REQUEST" {
return nil, fmt.Errorf("invalid PEM-encoded certificate signing request")
}
Expand All @@ -131,6 +129,26 @@ func builtinCryptoX509ParseCertificateRequest(a ast.Value) (ast.Value, error) {
return ast.InterfaceToValue(x)
}

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(a ast.Value) (ast.Value, error) {
return hashHelper(a, func(s ast.String) string { return fmt.Sprintf("%x", md5.Sum([]byte(s))) })
}

func builtinCryptoSha1(a ast.Value) (ast.Value, error) {
return hashHelper(a, func(s ast.String) string { return fmt.Sprintf("%x", sha1.Sum([]byte(s))) })
}

func builtinCryptoSha256(a ast.Value) (ast.Value, error) {
return hashHelper(a, func(s ast.String) string { return fmt.Sprintf("%x", sha256.Sum256([]byte(s))) })
}

func init() {
RegisterFunctionalBuiltin1(ast.CryptoX509ParseCertificates.Name, builtinCryptoX509ParseCertificates)
RegisterFunctionalBuiltin1(ast.CryptoMd5.Name, builtinCryptoMd5)
Expand Down
Loading

0 comments on commit 01b6e21

Please sign in to comment.