Vault-17911: add support for hex values in oid extension (#21830)

* add support for hex values in oid extension

* add changelog

* add length check on split and error handling on unmarshal
This commit is contained in:
Rachel Culpepper
2023-07-17 10:58:18 -04:00
committed by GitHub
parent adf0361203
commit 71841c51be
4 changed files with 44 additions and 9 deletions

View File

@@ -14,6 +14,7 @@ import (
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"github.com/hashicorp/go-sockaddr"
"io" "io"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
@@ -28,8 +29,6 @@ import (
"time" "time"
"github.com/go-test/deep" "github.com/go-test/deep"
"github.com/hashicorp/go-sockaddr"
"golang.org/x/net/http2" "golang.org/x/net/http2"
cleanhttp "github.com/hashicorp/go-cleanhttp" cleanhttp "github.com/hashicorp/go-cleanhttp"
@@ -1306,6 +1305,12 @@ func TestBackend_ext_singleCert(t *testing.T) {
testAccStepLoginInvalid(t, connState), testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid", ext: "2.1.1.1:*,2.1.1.2:The Wrong Value"}, false), testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid", ext: "2.1.1.1:*,2.1.1.2:The Wrong Value"}, false),
testAccStepLoginInvalid(t, connState), testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "hex:2.5.29.17:*87047F000002*"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "hex:2.5.29.17:*87047F000001*"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "2.5.29.17:"}, false),
testAccStepLogin(t, connState),
testAccStepReadConfig(t, config{EnableIdentityAliasMetadata: false}, connState), testAccStepReadConfig(t, config{EnableIdentityAliasMetadata: false}, connState),
testAccStepCert(t, "web", ca, "foo", allowed{metadata_ext: "2.1.1.1,1.2.3.45"}, false), testAccStepCert(t, "web", ca, "foo", allowed{metadata_ext: "2.1.1.1,1.2.3.45"}, false),
testAccStepLoginWithMetadata(t, connState, "web", map[string]string{"2-1-1-1": "A UTF8String Extension"}, false), testAccStepLoginWithMetadata(t, connState, "web", map[string]string{"2-1-1-1": "A UTF8String Extension"}, false),

View File

@@ -10,6 +10,7 @@ import (
"crypto/x509" "crypto/x509"
"encoding/asn1" "encoding/asn1"
"encoding/base64" "encoding/base64"
"encoding/hex"
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
@@ -507,18 +508,43 @@ func (b *backend) matchesCertificateExtensions(clientCert *x509.Certificate, con
// including its ASN.1 type tag bytes. For the sake of simplicity, assume string type // including its ASN.1 type tag bytes. For the sake of simplicity, assume string type
// and drop the tag bytes. And get the number of bytes from the tag. // and drop the tag bytes. And get the number of bytes from the tag.
clientExtMap := make(map[string]string, len(clientCert.Extensions)) clientExtMap := make(map[string]string, len(clientCert.Extensions))
hexExtMap := make(map[string]string, len(clientCert.Extensions))
for _, ext := range clientCert.Extensions { for _, ext := range clientCert.Extensions {
var parsedValue string var parsedValue string
asn1.Unmarshal(ext.Value, &parsedValue) _, err := asn1.Unmarshal(ext.Value, &parsedValue)
clientExtMap[ext.Id.String()] = parsedValue if err != nil {
clientExtMap[ext.Id.String()] = ""
} else {
clientExtMap[ext.Id.String()] = parsedValue
}
hexExtMap[ext.Id.String()] = hex.EncodeToString(ext.Value)
} }
// If any of the required extensions don'log match the constraint fails
// If any of the required extensions don't match the constraint fails
for _, requiredExt := range config.Entry.RequiredExtensions { for _, requiredExt := range config.Entry.RequiredExtensions {
reqExt := strings.SplitN(requiredExt, ":", 2) reqExt := strings.SplitN(requiredExt, ":", 2)
clientExtValue, clientExtValueOk := clientExtMap[reqExt[0]] if len(reqExt) != 2 {
if !clientExtValueOk || !glob.Glob(reqExt[1], clientExtValue) {
return false return false
} }
if reqExt[0] == "hex" {
reqHexExt := strings.SplitN(reqExt[1], ":", 2)
if len(reqHexExt) != 2 {
return false
}
clientExtValue, clientExtValueOk := hexExtMap[reqHexExt[0]]
if !clientExtValueOk || !glob.Glob(strings.ToLower(reqHexExt[1]), clientExtValue) {
return false
}
} else {
clientExtValue, clientExtValueOk := clientExtMap[reqExt[0]]
if !clientExtValueOk || !glob.Glob(reqExt[1], clientExtValue) {
return false
}
}
} }
return true return true
} }

3
changelog/21830.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:improvement
auth/cert: Adds support for requiring hexadecimal-encoded non-string certificate extension values
```

View File

@@ -59,8 +59,9 @@ Sets a CA cert and associated parameters in a role name.
- `required_extensions` `(string: "" or array: [])` - Require specific Custom - `required_extensions` `(string: "" or array: [])` - Require specific Custom
Extension OIDs to exist and match the pattern. Value is a comma separated Extension OIDs to exist and match the pattern. Value is a comma separated
string or array of `oid:value`. Expects the extension value to be some type string or array of `oid:value`. Expects the extension value to be some type
of ASN1 encoded string. All conditions _must_ be met. Supports globbing on of ASN1 encoded string. All conditions _must_ be met. To match on the hex-encoded
`value`. value of the extension, including non-string extensions, use the format
`hex:<oid>:<value>`.Supports globbing on `value`.
- `allowed_metadata_extensions` `(array:[])` - A comma separated string or - `allowed_metadata_extensions` `(array:[])` - A comma separated string or
array of oid extensions. Upon successful authentication, these extensions array of oid extensions. Upon successful authentication, these extensions
will be added as metadata if they are present in the certificate. The will be added as metadata if they are present in the certificate. The