mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-10 23:57:34 +02:00
add mTLS UserPrincipalName SAN match (#5177)
Add a new 'user_principal_name' type to the downstream mTLS match_subject_alt_names option. This corresponds to the 'OtherName' type with type-id 1.3.6.1.4.1.311.20.2.3 and a UTF8String value. Add support for UserPrincipalName SAN matching to the policy evaluator.
This commit is contained in:
parent
b0606d9283
commit
dbedfc586f
9 changed files with 416 additions and 84 deletions
|
@ -3,14 +3,18 @@ package evaluator
|
|||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
cb_asn1 "golang.org/x/crypto/cryptobyte/asn1"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
|
@ -135,6 +139,16 @@ func verifyClientCertificate(
|
|||
crls map[string]*x509.RevocationList,
|
||||
constraints ClientCertConstraints,
|
||||
) error {
|
||||
// If a SubjectAltName extension is:
|
||||
// - marked as critical, and
|
||||
// - contains only name types that are not recognized by the Go standard
|
||||
// library (i.e. no DNS, email address, IP address, or URI names)
|
||||
// then the Go parsing code will add it to the UnhandleCriticalExtensions
|
||||
// field of the Certificate struct. This will fail the Verify() call below.
|
||||
// Because we support other SAN matching checks, let's avoid this behavior.
|
||||
cert.UnhandledCriticalExtensions = slices.DeleteFunc(cert.UnhandledCriticalExtensions,
|
||||
func(oid asn1.ObjectIdentifier) bool { return oid.Equal(oidSubjectAltName) })
|
||||
|
||||
chains, err := cert.Verify(x509.VerifyOptions{
|
||||
Roots: roots,
|
||||
Intermediates: intermediates,
|
||||
|
@ -242,6 +256,17 @@ func validateClientCertificateSANs(chain []*x509.Certificate, matchers SANMatche
|
|||
}
|
||||
}
|
||||
}
|
||||
if r := matchers[config.SANTypeUserPrincipalName]; r != nil {
|
||||
names, err := getUserPrincipalNamesFromCert(cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range names {
|
||||
if r.MatchString(name) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errNoSANMatch
|
||||
}
|
||||
|
@ -256,3 +281,56 @@ func parseCertificate(pemStr string) (*x509.Certificate, error) {
|
|||
}
|
||||
return x509.ParseCertificate(block.Bytes)
|
||||
}
|
||||
|
||||
var (
|
||||
oidSubjectAltName = asn1.ObjectIdentifier{2, 5, 29, 17}
|
||||
oidUserPrincipalName = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 20, 2, 3}
|
||||
otherNameTag = cb_asn1.Tag(0).Constructed().ContextSpecific()
|
||||
otherNameValueTag = cb_asn1.Tag(0).Constructed().ContextSpecific()
|
||||
)
|
||||
|
||||
func getUserPrincipalNamesFromSAN(raw []byte) ([]string, error) {
|
||||
san := cryptobyte.String(raw)
|
||||
var generalNames cryptobyte.String
|
||||
if !san.ReadASN1(&generalNames, cb_asn1.SEQUENCE) {
|
||||
return nil, errors.New("error reading GeneralNames sequence")
|
||||
}
|
||||
var upns []string
|
||||
for !generalNames.Empty() {
|
||||
var name cryptobyte.String
|
||||
var tag cb_asn1.Tag
|
||||
if !generalNames.ReadAnyASN1(&name, &tag) {
|
||||
return nil, errors.New("error reading GeneralName")
|
||||
} else if tag != otherNameTag {
|
||||
continue
|
||||
}
|
||||
|
||||
var oid asn1.ObjectIdentifier
|
||||
if !name.ReadASN1ObjectIdentifier(&oid) {
|
||||
return nil, errors.New("error reading OtherName type ID")
|
||||
} else if !oid.Equal(oidUserPrincipalName) {
|
||||
continue
|
||||
}
|
||||
|
||||
var value cryptobyte.String
|
||||
if !name.ReadASN1(&value, otherNameValueTag) {
|
||||
return nil, errors.New("error reading UserPrincipalName value")
|
||||
}
|
||||
|
||||
var utf8string cryptobyte.String
|
||||
if !value.ReadASN1(&utf8string, cb_asn1.UTF8String) {
|
||||
return nil, errors.New("error reading UserPrincipalName: expected UTF8String")
|
||||
}
|
||||
upns = append(upns, string(utf8string))
|
||||
}
|
||||
return upns, nil
|
||||
}
|
||||
|
||||
func getUserPrincipalNamesFromCert(cert *x509.Certificate) ([]string, error) {
|
||||
for _, ext := range cert.Extensions {
|
||||
if ext.Id.Equal(oidSubjectAltName) {
|
||||
return getUserPrincipalNamesFromSAN(ext.Value)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue