mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-01 13:39:37 +08:00
333 lines
9.5 KiB
Go
333 lines
9.5 KiB
Go
// Copyright 2022 Gravitational, Inc
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package webauthnwin
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"syscall"
|
|
"unicode/utf16"
|
|
|
|
"github.com/go-webauthn/webauthn/protocol"
|
|
"github.com/gravitational/trace"
|
|
)
|
|
|
|
func assertOptionsToCType(in protocol.PublicKeyCredentialRequestOptions, loginOpts *LoginOpts) (*webauthnAuthenticatorGetAssertionOptions, error) {
|
|
allowCredList, err := credentialsExToCType(in.AllowedCredentials)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var dwAuthenticatorAttachment uint32
|
|
if loginOpts != nil {
|
|
switch loginOpts.AuthenticatorAttachment {
|
|
case AttachmentPlatform:
|
|
dwAuthenticatorAttachment = 1
|
|
case AttachmentCrossPlatform:
|
|
dwAuthenticatorAttachment = 2
|
|
}
|
|
}
|
|
|
|
return &webauthnAuthenticatorGetAssertionOptions{
|
|
// https://github.com/microsoft/webauthn/blob/7ab979cc833bfab9a682ed51761309db57f56c8c/webauthn.h#L36-L97
|
|
// contains information about different versions.
|
|
// We can set newest version and it still works on older APIs.
|
|
dwVersion: 6,
|
|
dwTimeoutMilliseconds: uint32(in.Timeout),
|
|
dwAuthenticatorAttachment: dwAuthenticatorAttachment,
|
|
dwUserVerificationRequirement: userVerificationToCType(in.UserVerification),
|
|
// TODO(tobiaszheller): support U2fAppId.
|
|
pAllowCredentialList: allowCredList,
|
|
}, nil
|
|
}
|
|
|
|
func rpToCType(in protocol.RelyingPartyEntity) (*webauthnRPEntityInformation, error) {
|
|
if in.ID == "" {
|
|
return nil, trace.BadParameter("missing RelyingPartyEntity.Id")
|
|
}
|
|
if in.Name == "" {
|
|
return nil, trace.BadParameter("missing RelyingPartyEntity.Name")
|
|
}
|
|
id, err := utf16PtrFromString(in.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
name, err := utf16PtrFromString(in.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var icon *uint16
|
|
if in.Icon != "" {
|
|
icon, err = utf16PtrFromString(in.Icon)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return &webauthnRPEntityInformation{
|
|
dwVersion: 1,
|
|
pwszID: id,
|
|
pwszName: name,
|
|
pwszIcon: icon,
|
|
}, nil
|
|
}
|
|
|
|
func userToCType(in protocol.UserEntity) (*webauthnUserEntityInformation, error) {
|
|
if len(in.ID) == 0 {
|
|
return nil, trace.BadParameter("missing UserEntity.Id")
|
|
}
|
|
if in.Name == "" {
|
|
return nil, trace.BadParameter("missing UserEntity.Name")
|
|
}
|
|
|
|
name, err := utf16PtrFromString(in.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var displayName *uint16
|
|
if in.DisplayName != "" {
|
|
displayName, err = utf16PtrFromString(in.DisplayName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
var icon *uint16
|
|
if in.Icon != "" {
|
|
icon, err = utf16PtrFromString(in.Icon)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return &webauthnUserEntityInformation{
|
|
dwVersion: 1,
|
|
cbID: uint32(len(in.ID)),
|
|
pbID: &in.ID[0],
|
|
pwszName: name,
|
|
pwszDisplayName: displayName,
|
|
pwszIcon: icon,
|
|
}, nil
|
|
}
|
|
|
|
func credParamToCType(in []protocol.CredentialParameter) (*webauthnCoseCredentialParameters, error) {
|
|
if len(in) == 0 {
|
|
return nil, trace.BadParameter("missing CredentialParameter")
|
|
}
|
|
out := make([]webauthnCoseCredentialParameter, 0, len(in))
|
|
for _, c := range in {
|
|
pwszCredentialType, err := utf16PtrFromString(string(c.Type))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, webauthnCoseCredentialParameter{
|
|
dwVersion: 1,
|
|
pwszCredentialType: pwszCredentialType,
|
|
lAlg: int32(c.Algorithm),
|
|
})
|
|
}
|
|
return &webauthnCoseCredentialParameters{
|
|
cCredentialParameters: uint32(len(out)),
|
|
pCredentialParameters: &out[0],
|
|
}, nil
|
|
}
|
|
|
|
func clientDataToCType(challenge, origin, cdType string) (*webauthnClientData, []byte, error) {
|
|
if challenge == "" {
|
|
return nil, nil, trace.BadParameter("missing ClientData.Challenge")
|
|
}
|
|
if origin == "" {
|
|
return nil, nil, trace.BadParameter("missing ClientData.Origin")
|
|
}
|
|
algID, err := utf16PtrFromString("SHA-256")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
type clientDataJSON struct {
|
|
Type string `json:"type"`
|
|
Challenge string `json:"challenge"`
|
|
Origin string `json:"origin"`
|
|
}
|
|
cd := clientDataJSON{
|
|
Type: cdType,
|
|
Challenge: challenge,
|
|
Origin: origin,
|
|
}
|
|
jsonCD, err := json.Marshal(cd)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return &webauthnClientData{
|
|
dwVersion: 1,
|
|
cbClientDataJSON: uint32(len(jsonCD)),
|
|
pbClientDataJSON: &jsonCD[0],
|
|
pwszHashAlgID: algID,
|
|
}, jsonCD, nil
|
|
}
|
|
|
|
func credentialsExToCType(in []protocol.CredentialDescriptor) (*webauthnCredentialList, error) {
|
|
exCredList := make([]*webauthnCredentialEX, 0, len(in))
|
|
for _, e := range in {
|
|
if e.Type == "" {
|
|
return nil, trace.BadParameter("missing CredentialDescriptor.Type")
|
|
}
|
|
if len(e.CredentialID) == 0 {
|
|
return nil, trace.BadParameter("missing CredentialDescriptor.CredentialID")
|
|
}
|
|
pwszCredentialType, err := utf16PtrFromString(string(e.Type))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
exCredList = append(exCredList, &webauthnCredentialEX{
|
|
dwVersion: 1,
|
|
cbID: uint32(len(e.CredentialID)),
|
|
pbID: &e.CredentialID[0],
|
|
pwszCredentialType: pwszCredentialType,
|
|
dwTransports: transportsToCType(e.Transport),
|
|
})
|
|
}
|
|
|
|
if len(exCredList) == 0 {
|
|
return nil, nil
|
|
}
|
|
return &webauthnCredentialList{
|
|
cCredentials: uint32(len(exCredList)),
|
|
ppCredentials: &exCredList[0],
|
|
}, nil
|
|
}
|
|
|
|
func transportsToCType(in []protocol.AuthenticatorTransport) uint32 {
|
|
if len(in) == 0 {
|
|
return 0
|
|
}
|
|
var out uint32
|
|
for _, at := range in {
|
|
// Mappped based on:
|
|
// https://github.com/microsoft/webauthn/blob/7ab979cc833bfab9a682ed51761309db57f56c8c/webauthn.h#L249-L254
|
|
switch at {
|
|
case protocol.USB:
|
|
out |= 0x1
|
|
case protocol.NFC:
|
|
out |= 0x2
|
|
case protocol.BLE:
|
|
out |= 0x4
|
|
case protocol.Internal:
|
|
out |= 0x10
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func attachmentToCType(in protocol.AuthenticatorAttachment) uint32 {
|
|
// Mapped based on:
|
|
// https://github.com/microsoft/webauthn/blob/7ab979cc833bfab9a682ed51761309db57f56c8c/webauthn.h#L493-L496
|
|
switch in {
|
|
case protocol.Platform:
|
|
return webauthnAttachmentPlatform
|
|
case protocol.CrossPlatform:
|
|
return webauthnAttachmentCrossPlatform
|
|
default:
|
|
return webauthnAttachmentAny
|
|
}
|
|
}
|
|
|
|
func conveyancePreferenceToCType(in protocol.ConveyancePreference) uint32 {
|
|
// Mapped based on:
|
|
// https://github.com/microsoft/webauthn/blob/7ab979cc833bfab9a682ed51761309db57f56c8c/webauthn.h#L503-L506
|
|
switch in {
|
|
case protocol.PreferNoAttestation:
|
|
return 1
|
|
case protocol.PreferIndirectAttestation:
|
|
return 2
|
|
case protocol.PreferDirectAttestation:
|
|
return 3
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func userVerificationToCType(in protocol.UserVerificationRequirement) uint32 {
|
|
// Mapped based on:
|
|
// https://github.com/microsoft/webauthn/blob/7ab979cc833bfab9a682ed51761309db57f56c8c/webauthn.h#L498-L501
|
|
switch in {
|
|
case protocol.VerificationRequired:
|
|
return webauthnUserVerificationRequired
|
|
case protocol.VerificationPreferred:
|
|
return webauthnUserVerificationPreferred
|
|
case protocol.VerificationDiscouraged:
|
|
return webauthnUserVerificationDiscouraged
|
|
default:
|
|
return webauthnUserVerificationAny
|
|
}
|
|
}
|
|
|
|
func requirePreferResidentKey(in protocol.AuthenticatorSelection) (requireRK bool, preferRK bool) {
|
|
switch in.ResidentKey {
|
|
case protocol.ResidentKeyRequirementRequired:
|
|
return true, false
|
|
case protocol.ResidentKeyRequirementPreferred:
|
|
return false, true
|
|
case protocol.ResidentKeyRequirementDiscouraged:
|
|
return false, false
|
|
default:
|
|
if in.RequireResidentKey != nil && *in.RequireResidentKey {
|
|
return true, false
|
|
}
|
|
return false, false
|
|
}
|
|
}
|
|
|
|
func makeCredOptionsToCType(in protocol.PublicKeyCredentialCreationOptions) (*webauthnAuthenticatorMakeCredentialOptions, error) {
|
|
exCredList, err := credentialsExToCType(in.CredentialExcludeList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
requiredRK, preferRK := requirePreferResidentKey(in.AuthenticatorSelection)
|
|
return &webauthnAuthenticatorMakeCredentialOptions{
|
|
// https://github.com/microsoft/webauthn/blob/7ab979cc833bfab9a682ed51761309db57f56c8c/webauthn.h#L36-L97
|
|
// contains information about different versions.
|
|
// We can set newest version and it still works on older APIs.
|
|
dwVersion: 5,
|
|
dwTimeoutMilliseconds: uint32(in.Timeout),
|
|
dwAuthenticatorAttachment: attachmentToCType(in.AuthenticatorSelection.AuthenticatorAttachment),
|
|
dwAttestationConveyancePreference: conveyancePreferenceToCType(in.Attestation),
|
|
bRequireResidentKey: boolToUint32(requiredRK),
|
|
dwUserVerificationRequirement: userVerificationToCType(in.AuthenticatorSelection.UserVerification),
|
|
pExcludeCredentialList: exCredList,
|
|
bPreferResidentKey: boolToUint32(preferRK),
|
|
}, nil
|
|
}
|
|
|
|
func boolToUint32(in bool) uint32 {
|
|
if in {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// utf16PtrFromString is copied from golang.org/x/sys/windows because we want
|
|
// to test conversions on linux machines also.
|
|
func utf16PtrFromString(s string) (*uint16, error) {
|
|
utf16FromString := func(s string) ([]uint16, error) {
|
|
if strings.IndexByte(s, 0) != -1 {
|
|
return nil, syscall.EINVAL
|
|
}
|
|
return utf16.Encode([]rune(s + "\x00")), nil
|
|
}
|
|
a, err := utf16FromString(s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &a[0], nil
|
|
}
|