Module: Ruby::Rego::Builtins::Crypto

Extended by:
RegistryHelpers
Defined in:
lib/ruby/rego/builtins/crypto.rb,
lib/ruby/rego/builtins/crypto/keys.rb,
lib/ruby/rego/builtins/crypto/verify.rb,
lib/ruby/rego/builtins/crypto/keypair.rb,
lib/ruby/rego/builtins/crypto/certificates.rb,
lib/ruby/rego/builtins/crypto/certificate_uri.rb,
lib/ruby/rego/builtins/crypto/certificate_name.rb,
lib/ruby/rego/builtins/crypto/certificate_struct.rb,
lib/ruby/rego/builtins/crypto/certificate_request.rb,
lib/ruby/rego/builtins/crypto/certificate_extensions.rb,
lib/ruby/rego/builtins/crypto/certificate_public_key.rb,
lib/ruby/rego/builtins/crypto/certificate_request_struct.rb

Overview

crypto.x509.parse_certificate_request — parse one PEM / base64-DER PKCS#10 certificate request into the JSON shape OPA emits: json.Marshal of Go’s x509.CertificateRequest (a single object, not an array). Reuses the certificate machinery’s atoms (Name, public key, signature algorithm, the ASN.1 depth/type guards, the SAN validators, scrub) since a CSR is a strict subset of a certificate’s fields — no validity, issuer, or the dozen parsed extensions a certificate carries.

Defined Under Namespace

Modules: CertificateStruct, Name Classes: MalformedCertificate

Constant Summary collapse

HMAC_ALGORITHMS =

Maps each crypto.hmac.* digest builtin to its OpenSSL digest name.

{
  "crypto.hmac.md5" => "MD5",
  "crypto.hmac.sha1" => "SHA1",
  "crypto.hmac.sha256" => "SHA256",
  "crypto.hmac.sha512" => "SHA512"
}.freeze
CRYPTO_FUNCTIONS =
{
  "crypto.md5" => { arity: 1, handler: :md5 },
  "crypto.sha1" => { arity: 1, handler: :sha1 },
  "crypto.sha256" => { arity: 1, handler: :sha256 },
  "crypto.hmac.md5" => { arity: 2, handler: :hmac_md5 },
  "crypto.hmac.sha1" => { arity: 2, handler: :hmac_sha1 },
  "crypto.hmac.sha256" => { arity: 2, handler: :hmac_sha256 },
  "crypto.hmac.sha512" => { arity: 2, handler: :hmac_sha512 },
  "crypto.hmac.equal" => { arity: 2, handler: :hmac_equal },
  "crypto.x509.parse_rsa_private_key" => { arity: 1, handler: :parse_rsa_private_key },
  "crypto.parse_private_keys" => { arity: 1, handler: :parse_private_keys }
}.freeze
RECOGNIZED_KEY_BLOCKS =

PEM block types getPrivateKeysFromPEMData parses; every other type is skipped, not failed.

["RSA PRIVATE KEY", "PRIVATE KEY", "EC PRIVATE KEY"].freeze
PEM_START =

The pem.Decode marker sentinels (Go’s pemStart / pemEnd / pemEndOfLine), with and without the leading newline that anchors a marker to the start of a line.

"\n-----BEGIN "
PEM_START_NOLF =
"-----BEGIN "
PEM_END =
"\n-----END "
PEM_END_NOLF =
"-----END "
PEM_EOL =
"-----"
BASE64_SKIP =

The bytes Go’s base64 decoder treats as insignificant (removeSpacesAndTabs strips “ \t”; the decoder ignores “\r\n”), and the complement of the std-base64 alphabet (plus padding and those skip bytes) — a byte matching it inside a block body dooms the decode.

" \t\r\n"
NON_BASE64_BYTE =
%r{[^A-Za-z0-9+/= \t\r\n]}
EC_CURVE_NAMES =

JWK “crv” names (RFC 7518) for the curves the JWK encoding covers (P-224 is excluded).

{
  "prime256v1" => "P-256",
  "secp384r1" => "P-384",
  "secp521r1" => "P-521"
}.freeze
EC_STRUCT_CURVES =

The EC curves Go’s crypto/elliptic supports (so x509 parses them): the JWK curves plus P-224, which the Go-struct output handles even though the JWK encoding does not.

(EC_CURVE_NAMES.keys + ["secp224r1"]).freeze
BLOCK_FORMAT_ELEMENT =

Go dispatches each PEM block type to ONE x509 parser (PKCS#1 / PKCS#8 / SEC1), so a DER in the wrong format for its label is rejected even though OpenSSL’s format-agnostic reader would accept it. The three formats are distinguished by their second ASN.1 element: PKCS#1’s is the modulus INTEGER, PKCS#8’s is the algorithm SEQUENCE, SEC1’s is the private-key OCTET STRING.

{
  "RSA PRIVATE KEY" => OpenSSL::ASN1::Integer,
  "PRIVATE KEY" => OpenSSL::ASN1::Sequence,
  "EC PRIVATE KEY" => OpenSSL::ASN1::OctetString
}.freeze
MAX_KEY_DER_BYTES =

Upper bound on the leading ASN.1 element a block may parse to before OpenSSL::PKey.read is attempted (DoS guard — see read_key). 64 KiB is far above any real private key (an RSA-16384 DER is ~9 KB), so only an absurd >130K-bit key would exceed it.

64 * 1024
VERIFY_FUNCTIONS =
{
  "crypto.x509.parse_and_verify_certificates" =>
    { arity: 1, handler: :parse_and_verify_certificates },
  "crypto.x509.parse_and_verify_certificates_with_options" =>
    { arity: 2, handler: :parse_and_verify_certificates_with_options }
}.freeze
INSECURE_SIGNATURE =

Signature digests Go’s x509 rejects as insecure (InsecureAlgorithmError) on non-root certs. The token appears mid-word in OpenSSL’s algorithm names (sha1WithRSAEncryption, ecdsa-with-SHA1, md5WithRSAEncryption), and none of the safe names (sha256/384/512, …) contain it, so no anchors.

/md2|md5|sha1/i
KEY_USAGE_ENUM =

OPA’s KeyUsages option enum -> Go’s ExtKeyUsage integer (the value CertificateStruct emits in a cert’s ExtKeyUsage field). KeyUsageAny (0) drops the EKU constraint entirely. The integers MUST match CertificateStruct::EXT_KEY_USAGES (the OID->enum map the chain structs are built with).

{
  "KeyUsageAny" => 0, "KeyUsageServerAuth" => 1, "KeyUsageClientAuth" => 2,
  "KeyUsageCodeSigning" => 3, "KeyUsageEmailProtection" => 4, "KeyUsageIPSECEndSystem" => 5,
  "KeyUsageIPSECTunnel" => 6, "KeyUsageIPSECUser" => 7, "KeyUsageTimeStamping" => 8,
  "KeyUsageOCSPSigning" => 9, "KeyUsageMicrosoftServerGatedCrypto" => 10,
  "KeyUsageNetscapeServerGatedCrypto" => 11, "KeyUsageMicrosoftCommercialCodeSigning" => 12,
  "KeyUsageMicrosoftKernelCodeSigning" => 13
}.freeze
ANY_EXT_KEY_USAGE =

Go’s ExtKeyUsageAny

0
SERVER_AUTH_EXT_KEY_USAGE =

Go’s empty-KeyUsages default

1
VERIFY_OPTION_KEYS =

The option keys OPA 1.17 accepts; any other key (including the doc-mentioned MaxPathLen, which this OPA version does not recognize) makes the call undefined.

%w[DNSName CurrentTime KeyUsages].freeze
KEYPAIR_FUNCTIONS =
{
  "crypto.x509.parse_keypair" => { arity: 2, handler: :parse_keypair }
}.freeze
CERTIFICATE_FUNCTIONS =
{
  "crypto.x509.parse_certificates" => { arity: 1, handler: :parse_certificates }
}.freeze
REQUEST_FUNCTIONS =
{
  "crypto.x509.parse_certificate_request" => { arity: 1, handler: :parse_certificate_request }
}.freeze

Class Method Summary collapse

Methods included from RegistryHelpers

register_configured_functions

Class Method Details

.hmac_equal(mac1_value, mac2_value) ⇒ Ruby::Rego::BooleanValue

Constant-time comparison of two MAC strings (OPA’s crypto.hmac.equal). Uses OpenSSL.secure_compare, which is timing-safe and returns false (rather than raising) for unequal-length inputs, matching Go’s hmac.Equal.

Parameters:

Returns:



120
121
122
123
124
# File 'lib/ruby/rego/builtins/crypto.rb', line 120

def self.hmac_equal(mac1_value, mac2_value)
  left = string_value(mac1_value, "crypto.hmac.equal")
  right = string_value(mac2_value, "crypto.hmac.equal")
  BooleanValue.new(OpenSSL.secure_compare(left, right))
end

.hmac_md5(message_value, key_value) ⇒ Ruby::Rego::StringValue

Parameters:

Returns:



73
74
75
# File 'lib/ruby/rego/builtins/crypto.rb', line 73

def self.hmac_md5(message_value, key_value)
  hmac(message_value, key_value, "crypto.hmac.md5")
end

.hmac_sha1(message_value, key_value) ⇒ Ruby::Rego::StringValue

Parameters:

Returns:



80
81
82
# File 'lib/ruby/rego/builtins/crypto.rb', line 80

def self.hmac_sha1(message_value, key_value)
  hmac(message_value, key_value, "crypto.hmac.sha1")
end

.hmac_sha256(message_value, key_value) ⇒ Ruby::Rego::StringValue

Parameters:

Returns:



87
88
89
# File 'lib/ruby/rego/builtins/crypto.rb', line 87

def self.hmac_sha256(message_value, key_value)
  hmac(message_value, key_value, "crypto.hmac.sha256")
end

.hmac_sha512(message_value, key_value) ⇒ Ruby::Rego::StringValue

Parameters:

Returns:



94
95
96
# File 'lib/ruby/rego/builtins/crypto.rb', line 94

def self.hmac_sha512(message_value, key_value)
  hmac(message_value, key_value, "crypto.hmac.sha512")
end

.md5(value) ⇒ Ruby::Rego::StringValue

Parameters:

Returns:



54
55
56
# File 'lib/ruby/rego/builtins/crypto.rb', line 54

def self.md5(value)
  StringValue.new(Digest::MD5.hexdigest(string_value(value, "crypto.md5")))
end

.parse_and_verify_certificates(value) ⇒ Ruby::Rego::Value

Parameters:

Returns:



69
70
71
72
73
# File 'lib/ruby/rego/builtins/crypto/verify.rb', line 69

def self.parse_and_verify_certificates(value)
  string = string_value(value, "crypto.x509.parse_and_verify_certificates")
  # No-options: default usage ServerAuth, and each chain cert keeps the injected URIStrings field.
  verify_result(string, { key_usages: [SERVER_AUTH_EXT_KEY_USAGE] }, uri_strings: true)
end

.parse_and_verify_certificates_with_options(value, options_value) ⇒ Ruby::Rego::Value

:reek:NilCheck – :invalid is the bad-options sentinel mapped to OPA’s undefined.

Parameters:

Returns:



79
80
81
82
83
84
85
86
87
# File 'lib/ruby/rego/builtins/crypto/verify.rb', line 79

def self.parse_and_verify_certificates_with_options(value, options_value)
  string = string_value(value, "crypto.x509.parse_and_verify_certificates_with_options")
  opts = verify_options(options_value)
  return UndefinedValue.new unless opts.is_a?(Hash)

  # ...with_options marshals each chain cert as Go's raw x509.Certificate — WITHOUT the URIStrings
  # field parse_certificates injects (an OPA quirk: the two builtins differ here).
  verify_result(string, opts, uri_strings: false)
end

.parse_certificate_request(value) ⇒ Ruby::Rego::Value

:reek:NilCheck – nil is request_from’s parse-failure sentinel (-> OPA undefined).

Parameters:

Returns:



29
30
31
32
33
34
35
36
37
# File 'lib/ruby/rego/builtins/crypto/certificate_request.rb', line 29

def self.parse_certificate_request(value)
  string = string_value(value, "crypto.x509.parse_certificate_request")
  return UndefinedValue.new unless scannable?(string)

  request = request_from(string)
  return UndefinedValue.new if request.nil?

  build_request_struct(request)
end

.parse_certificates(value) ⇒ Ruby::Rego::Value

:reek:NilCheck – nil is certificates_from’s “a block failed to parse” sentinel (-> undefined).

Parameters:

Returns:



39
40
41
42
43
44
45
46
47
# File 'lib/ruby/rego/builtins/crypto/certificates.rb', line 39

def self.parse_certificates(value)
  string = string_value(value, "crypto.x509.parse_certificates")
  return UndefinedValue.new unless scannable?(string)

  certs = certificates_from(string)
  return UndefinedValue.new if certs.nil?

  build_structs(certs)
end

.parse_keypair(cert_value, key_value) ⇒ Ruby::Rego::Value

:reek:NilCheck – nil is the parse-failure sentinel from keypair_cert_ders / matching_key. :reek:TooManyStatements – a faithful port of OPA’s keypair parse + validation flow. rubocop:disable Metrics/AbcSize

Parameters:

Returns:



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/ruby/rego/builtins/crypto/keypair.rb', line 38

def self.parse_keypair(cert_value, key_value)
  cert_string = string_value(cert_value, "crypto.x509.parse_keypair")
  key_string = string_value(key_value, "crypto.x509.parse_keypair")
  return UndefinedValue.new unless scannable?(cert_string) && scannable?(key_string)

  cert_ders = keypair_cert_ders(cert_string)
  return UndefinedValue.new if cert_ders.nil?

  leaf = leaf_certificate(cert_ders.first)
  return UndefinedValue.new if leaf.nil?

  key = matching_key(leaf, key_string)
  return UndefinedValue.new if key.nil?

  build_keypair_struct(cert_ders, leaf, key)
end

.parse_private_keys(value) ⇒ Ruby::Rego::Value

:reek:TooManyStatements – a faithful port of OPA’s builtinCryptoParsePrivateKeys flow. :reek:NilCheck – nil is supported_keys’s “a recognized block failed or is unsupported” sentinel.

Parameters:

Returns:



81
82
83
84
85
86
87
88
89
90
# File 'lib/ruby/rego/builtins/crypto/keys.rb', line 81

def self.parse_private_keys(value)
  string = string_value(value, "crypto.parse_private_keys")
  return UndefinedValue.new unless scannable?(string)
  return NullValue.new if string.empty?

  keys = supported_keys(string)
  return UndefinedValue.new if keys.nil?

  Value.from_ruby(keys.map { |key| go_struct_for(key) })
end

.parse_rsa_private_key(value) ⇒ Ruby::Rego::Value

:reek:TooManyStatements – a faithful port of OPA’s builtinCryptoJWKFromPrivateKey flow. :reek:NilCheck – nil sentinels: bad base64/failed block (undefined), JWK-unsupported key (undefined). rubocop:disable Metrics/AbcSize

Parameters:

Returns:



97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/ruby/rego/builtins/crypto/keys.rb', line 97

def self.parse_rsa_private_key(value)
  string = string_value(value, "crypto.x509.parse_rsa_private_key")
  return UndefinedValue.new if !scannable?(string) || string.empty?

  pem = pem_or_base64(string)
  return UndefinedValue.new if pem.nil?

  keys = supported_keys(pem)
  return UndefinedValue.new if keys.nil?
  return NullValue.new if keys.empty?

  jwk = jwk_for(keys.first)
  jwk.nil? ? UndefinedValue.new : Value.from_ruby(jwk)
end

.register!Ruby::Rego::Builtins::BuiltinRegistry



44
45
46
47
48
# File 'lib/ruby/rego/builtins/crypto.rb', line 44

def self.register!
  registry = BuiltinRegistry.instance
  register_configured_functions(registry, CRYPTO_FUNCTIONS)
  registry
end

.register_certificates!Ruby::Rego::Builtins::BuiltinRegistry



30
31
32
33
34
# File 'lib/ruby/rego/builtins/crypto/certificates.rb', line 30

def self.register_certificates!
  registry = BuiltinRegistry.instance
  register_configured_functions(registry, CERTIFICATE_FUNCTIONS)
  registry
end

.register_keypairs!Ruby::Rego::Builtins::BuiltinRegistry



26
27
28
29
30
# File 'lib/ruby/rego/builtins/crypto/keypair.rb', line 26

def self.register_keypairs!
  registry = BuiltinRegistry.instance
  register_configured_functions(registry, KEYPAIR_FUNCTIONS)
  registry
end

.register_requests!Ruby::Rego::Builtins::BuiltinRegistry



20
21
22
23
24
# File 'lib/ruby/rego/builtins/crypto/certificate_request.rb', line 20

def self.register_requests!
  registry = BuiltinRegistry.instance
  register_configured_functions(registry, REQUEST_FUNCTIONS)
  registry
end

.register_verifications!Ruby::Rego::Builtins::BuiltinRegistry



61
62
63
64
65
# File 'lib/ruby/rego/builtins/crypto/verify.rb', line 61

def self.register_verifications!
  registry = BuiltinRegistry.instance
  register_configured_functions(registry, VERIFY_FUNCTIONS)
  registry
end

.sha1(value) ⇒ Ruby::Rego::StringValue

Parameters:

Returns:



60
61
62
# File 'lib/ruby/rego/builtins/crypto.rb', line 60

def self.sha1(value)
  StringValue.new(Digest::SHA1.hexdigest(string_value(value, "crypto.sha1")))
end

.sha256(value) ⇒ Ruby::Rego::StringValue

Parameters:

Returns:



66
67
68
# File 'lib/ruby/rego/builtins/crypto.rb', line 66

def self.sha256(value)
  StringValue.new(Digest::SHA256.hexdigest(string_value(value, "crypto.sha256")))
end