mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-30 02:46:30 +02:00
* remove source, remove deadcode, fix linting issues * use github action for lint * fix missing envoy
306 lines
9.7 KiB
Go
306 lines
9.7 KiB
Go
package webauthnutil
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/pomerium/pomerium/pkg/cryptutil"
|
|
"github.com/pomerium/pomerium/pkg/grpc/device"
|
|
"github.com/pomerium/pomerium/pkg/grpc/user"
|
|
"github.com/pomerium/pomerium/pkg/slices"
|
|
"github.com/pomerium/webauthn"
|
|
"github.com/pomerium/webauthn/cose"
|
|
)
|
|
|
|
const (
|
|
ceremonyTimeout = time.Minute * 15
|
|
rpName = "Pomerium"
|
|
)
|
|
|
|
// GenerateChallenge generates a new Challenge.
|
|
func GenerateChallenge(key []byte, expiry time.Time) cryptutil.SecureToken {
|
|
return cryptutil.GenerateSecureToken(key, expiry, cryptutil.NewRandomToken())
|
|
}
|
|
|
|
// GenerateCreationOptions generates creation options for WebAuthn.
|
|
func GenerateCreationOptions(
|
|
r *http.Request,
|
|
key []byte,
|
|
deviceType *device.Type,
|
|
user *user.User,
|
|
) *webauthn.PublicKeyCredentialCreationOptions {
|
|
expiry := time.Now().Add(ceremonyTimeout)
|
|
return newCreationOptions(
|
|
r,
|
|
GenerateChallenge(key, expiry).Bytes(),
|
|
deviceType,
|
|
user,
|
|
)
|
|
}
|
|
|
|
// GenerateRequestOptions generates request options for WebAuthn.
|
|
func GenerateRequestOptions(
|
|
r *http.Request,
|
|
key []byte,
|
|
deviceType *device.Type,
|
|
knownDeviceCredentials []*device.Credential,
|
|
) *webauthn.PublicKeyCredentialRequestOptions {
|
|
expiry := time.Now().Add(ceremonyTimeout)
|
|
return newRequestOptions(
|
|
r,
|
|
GenerateChallenge(key, expiry).Bytes(),
|
|
deviceType,
|
|
knownDeviceCredentials,
|
|
)
|
|
}
|
|
|
|
// GetCreationOptionsForCredential gets the creation options for the public key creation credential. An error may be
|
|
// returned if the challenge used to generate the credential is invalid.
|
|
func GetCreationOptionsForCredential(
|
|
r *http.Request,
|
|
key []byte,
|
|
deviceType *device.Type,
|
|
user *user.User,
|
|
credential *webauthn.PublicKeyCreationCredential,
|
|
) (*webauthn.PublicKeyCredentialCreationOptions, error) {
|
|
clientData, err := credential.Response.UnmarshalClientData()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid client data: %w", err)
|
|
}
|
|
|
|
rawChallenge, err := base64.RawURLEncoding.DecodeString(clientData.Challenge)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid challenge: %w", err)
|
|
}
|
|
var challenge cryptutil.SecureToken
|
|
copy(challenge[:], rawChallenge)
|
|
|
|
err = challenge.Verify(key, time.Now())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return newCreationOptions(r, challenge.Bytes(), deviceType, user), nil
|
|
}
|
|
|
|
// GetRequestOptionsForCredential gets the request options for the public key request credential. An error may be
|
|
// returned if the challenge used to generate the credential is invalid.
|
|
func GetRequestOptionsForCredential(
|
|
r *http.Request,
|
|
key []byte,
|
|
deviceType *device.Type,
|
|
knownDeviceCredentials []*device.Credential,
|
|
credential *webauthn.PublicKeyAssertionCredential,
|
|
) (*webauthn.PublicKeyCredentialRequestOptions, error) {
|
|
clientData, err := credential.Response.UnmarshalClientData()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid client data: %w", err)
|
|
}
|
|
|
|
rawChallenge, err := base64.RawURLEncoding.DecodeString(clientData.Challenge)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid challenge: %w", err)
|
|
}
|
|
var challenge cryptutil.SecureToken
|
|
copy(challenge[:], rawChallenge)
|
|
|
|
err = challenge.Verify(key, time.Now())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return newRequestOptions(r, challenge.Bytes(), deviceType, knownDeviceCredentials), nil
|
|
}
|
|
|
|
// newCreationOptions gets the creation options for WebAuthn with the provided challenge.
|
|
func newCreationOptions(
|
|
r *http.Request,
|
|
challenge []byte,
|
|
deviceType *device.Type,
|
|
user *user.User,
|
|
) *webauthn.PublicKeyCredentialCreationOptions {
|
|
options := &webauthn.PublicKeyCredentialCreationOptions{
|
|
RP: webauthn.PublicKeyCredentialRPEntity{
|
|
Name: rpName,
|
|
ID: GetEffectiveDomain(r),
|
|
},
|
|
User: GetUserEntity(user),
|
|
Challenge: challenge,
|
|
Timeout: ceremonyTimeout,
|
|
}
|
|
|
|
if deviceOptions := deviceType.GetWebauthn().GetOptions(); deviceOptions != nil {
|
|
fillAllPublicKeyCredentialParameters(options, deviceOptions.GetPubKeyCredParams())
|
|
fillAuthenticatorSelection(options, deviceOptions.GetAuthenticatorSelection())
|
|
fillAttestationConveyance(options, deviceOptions.Attestation)
|
|
}
|
|
|
|
return options
|
|
}
|
|
|
|
// newRequestOptions gets the request options for WebAuthn with the provided challenge.
|
|
func newRequestOptions(
|
|
r *http.Request,
|
|
challenge []byte,
|
|
deviceType *device.Type,
|
|
knownDeviceCredentials []*device.Credential,
|
|
) *webauthn.PublicKeyCredentialRequestOptions {
|
|
options := &webauthn.PublicKeyCredentialRequestOptions{
|
|
Challenge: challenge,
|
|
Timeout: ceremonyTimeout,
|
|
RPID: GetEffectiveDomain(r),
|
|
}
|
|
fillRequestUserVerificationRequirement(
|
|
options,
|
|
deviceType.GetWebauthn().GetOptions().GetAuthenticatorSelection().UserVerification,
|
|
)
|
|
knownDeviceCredentialsForType := slices.Filter(knownDeviceCredentials, func(c *device.Credential) bool {
|
|
return c.GetTypeId() == deviceType.GetId()
|
|
})
|
|
for _, knownDeviceCredential := range knownDeviceCredentialsForType {
|
|
if publicKey := knownDeviceCredential.GetWebauthn(); publicKey != nil {
|
|
options.AllowCredentials = append(options.AllowCredentials, webauthn.PublicKeyCredentialDescriptor{
|
|
Type: webauthn.PublicKeyCredentialTypePublicKey,
|
|
ID: publicKey.GetId(),
|
|
})
|
|
}
|
|
}
|
|
return options
|
|
}
|
|
|
|
func fillAllPublicKeyCredentialParameters(
|
|
options *webauthn.PublicKeyCredentialCreationOptions,
|
|
allDeviceParams []*device.WebAuthnOptions_PublicKeyCredentialParameters,
|
|
) {
|
|
options.PubKeyCredParams = nil
|
|
for _, deviceParams := range allDeviceParams {
|
|
p := webauthn.PublicKeyCredentialParameters{}
|
|
fillPublicKeyCredentialParameters(&p, deviceParams)
|
|
options.PubKeyCredParams = append(options.PubKeyCredParams, p)
|
|
}
|
|
}
|
|
|
|
func fillAttestationConveyance(
|
|
options *webauthn.PublicKeyCredentialCreationOptions,
|
|
attestationConveyance *device.WebAuthnOptions_AttestationConveyancePreference,
|
|
) {
|
|
options.Attestation = ""
|
|
if attestationConveyance == nil {
|
|
return
|
|
}
|
|
|
|
switch *attestationConveyance {
|
|
case device.WebAuthnOptions_NONE:
|
|
options.Attestation = webauthn.AttestationConveyanceNone
|
|
case device.WebAuthnOptions_INDIRECT:
|
|
options.Attestation = webauthn.AttestationConveyanceIndirect
|
|
case device.WebAuthnOptions_DIRECT:
|
|
options.Attestation = webauthn.AttestationConveyanceDirect
|
|
case device.WebAuthnOptions_ENTERPRISE:
|
|
options.Attestation = webauthn.AttestationConveyanceEnterprise
|
|
}
|
|
}
|
|
|
|
func fillAuthenticatorAttachment(
|
|
criteria *webauthn.AuthenticatorSelectionCriteria,
|
|
authenticatorAttachment *device.WebAuthnOptions_AuthenticatorAttachment,
|
|
) {
|
|
criteria.AuthenticatorAttachment = ""
|
|
if authenticatorAttachment == nil {
|
|
return
|
|
}
|
|
|
|
switch *authenticatorAttachment {
|
|
case device.WebAuthnOptions_CROSS_PLATFORM:
|
|
criteria.AuthenticatorAttachment = webauthn.AuthenticatorAttachmentCrossPlatform
|
|
case device.WebAuthnOptions_PLATFORM:
|
|
criteria.AuthenticatorAttachment = webauthn.AuthenticatorAttachmentPlatform
|
|
}
|
|
}
|
|
|
|
func fillAuthenticatorSelection(
|
|
options *webauthn.PublicKeyCredentialCreationOptions,
|
|
deviceCriteria *device.WebAuthnOptions_AuthenticatorSelectionCriteria,
|
|
) {
|
|
options.AuthenticatorSelection = new(webauthn.AuthenticatorSelectionCriteria)
|
|
fillAuthenticatorAttachment(options.AuthenticatorSelection, deviceCriteria.AuthenticatorAttachment)
|
|
fillResidentKeyRequirement(options.AuthenticatorSelection, deviceCriteria.ResidentKeyRequirement)
|
|
options.AuthenticatorSelection.RequireResidentKey = deviceCriteria.GetRequireResidentKey()
|
|
fillUserVerificationRequirement(options.AuthenticatorSelection, deviceCriteria.UserVerification)
|
|
}
|
|
|
|
func fillPublicKeyCredentialParameters(
|
|
params *webauthn.PublicKeyCredentialParameters,
|
|
deviceParams *device.WebAuthnOptions_PublicKeyCredentialParameters,
|
|
) {
|
|
params.Type = ""
|
|
params.COSEAlgorithmIdentifier = 0
|
|
if deviceParams == nil {
|
|
return
|
|
}
|
|
|
|
switch deviceParams.Type {
|
|
case device.WebAuthnOptions_PUBLIC_KEY:
|
|
params.Type = webauthn.PublicKeyCredentialTypePublicKey
|
|
}
|
|
params.COSEAlgorithmIdentifier = cose.Algorithm(deviceParams.GetAlg())
|
|
}
|
|
|
|
func fillRequestUserVerificationRequirement(
|
|
options *webauthn.PublicKeyCredentialRequestOptions,
|
|
userVerificationRequirement *device.WebAuthnOptions_UserVerificationRequirement,
|
|
) {
|
|
options.UserVerification = ""
|
|
if userVerificationRequirement == nil {
|
|
return
|
|
}
|
|
|
|
switch *userVerificationRequirement {
|
|
case device.WebAuthnOptions_USER_VERIFICATION_DISCOURAGED:
|
|
options.UserVerification = webauthn.UserVerificationDiscouraged
|
|
case device.WebAuthnOptions_USER_VERIFICATION_PREFERRED:
|
|
options.UserVerification = webauthn.UserVerificationPreferred
|
|
case device.WebAuthnOptions_USER_VERIFICATION_REQUIRED:
|
|
options.UserVerification = webauthn.UserVerificationRequired
|
|
}
|
|
}
|
|
|
|
func fillResidentKeyRequirement(
|
|
criteria *webauthn.AuthenticatorSelectionCriteria,
|
|
residentKeyRequirement *device.WebAuthnOptions_ResidentKeyRequirement,
|
|
) {
|
|
criteria.ResidentKey = ""
|
|
if residentKeyRequirement == nil {
|
|
return
|
|
}
|
|
|
|
switch *residentKeyRequirement {
|
|
case device.WebAuthnOptions_RESIDENT_KEY_DISCOURAGED:
|
|
criteria.ResidentKey = webauthn.ResidentKeyDiscouraged
|
|
case device.WebAuthnOptions_RESIDENT_KEY_PREFERRED:
|
|
criteria.ResidentKey = webauthn.ResidentKeyPreferred
|
|
case device.WebAuthnOptions_RESIDENT_KEY_REQUIRED:
|
|
criteria.ResidentKey = webauthn.ResidentKeyRequired
|
|
}
|
|
}
|
|
|
|
func fillUserVerificationRequirement(
|
|
criteria *webauthn.AuthenticatorSelectionCriteria,
|
|
userVerificationRequirement *device.WebAuthnOptions_UserVerificationRequirement,
|
|
) {
|
|
criteria.UserVerification = ""
|
|
if userVerificationRequirement == nil {
|
|
return
|
|
}
|
|
|
|
switch *userVerificationRequirement {
|
|
case device.WebAuthnOptions_USER_VERIFICATION_DISCOURAGED:
|
|
criteria.UserVerification = webauthn.UserVerificationDiscouraged
|
|
case device.WebAuthnOptions_USER_VERIFICATION_PREFERRED:
|
|
criteria.UserVerification = webauthn.UserVerificationPreferred
|
|
case device.WebAuthnOptions_USER_VERIFICATION_REQUIRED:
|
|
criteria.UserVerification = webauthn.UserVerificationRequired
|
|
}
|
|
}
|