diff --git a/go.mod b/go.mod index 92fdfd1..b97d09e 100644 --- a/go.mod +++ b/go.mod @@ -12,10 +12,12 @@ require ( github.com/google/uuid v1.6.0 github.com/mitchellh/mapstructure v1.5.0 github.com/stretchr/testify v1.10.0 + github.com/tinylib/msgp v1.3.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/x448/float16 v0.8.4 // indirect golang.org/x/crypto v0.38.0 // indirect diff --git a/go.sum b/go.sum index 538dcad..54c1703 100644 --- a/go.sum +++ b/go.sum @@ -12,10 +12,14 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww= +github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= diff --git a/protocol/options.go b/protocol/options.go index a070e7c..8648420 100644 --- a/protocol/options.go +++ b/protocol/options.go @@ -1,9 +1,5 @@ package protocol -import ( - "github.com/go-webauthn/webauthn/protocol/webauthncose" -) - type CredentialCreation struct { Response PublicKeyCredentialCreationOptions `json:"publicKey"` Mediation CredentialMediationRequirement `json:"mediation,omitempty"` @@ -73,13 +69,6 @@ type CredentialDescriptor struct { AttestationType string `json:"-"` } -// CredentialParameter is the credential type and algorithm -// that the relying party wants the authenticator to create. -type CredentialParameter struct { - Type CredentialType `json:"type"` - Algorithm webauthncose.COSEAlgorithmIdentifier `json:"alg"` -} - // CredentialType represents the PublicKeyCredentialType IDL and is used with the CredentialDescriptor IDL. // // This enumeration defines the valid credential types. It is an extension point; values can be added to it in the diff --git a/protocol/options_credparam.go b/protocol/options_credparam.go new file mode 100644 index 0000000..027b499 --- /dev/null +++ b/protocol/options_credparam.go @@ -0,0 +1,14 @@ +package protocol + +import "github.com/go-webauthn/webauthn/protocol/webauthncose" + +//go:generate msgp +//msgp:replace CredentialType with:string +//msgp:replace webauthncose.COSEAlgorithmIdentifier with:int + +// CredentialParameter is the credential type and algorithm +// that the relying party wants the authenticator to create. +type CredentialParameter struct { + Type CredentialType `json:"type" msg:"type"` + Algorithm webauthncose.COSEAlgorithmIdentifier `json:"alg" msg:"alg"` +} diff --git a/protocol/options_credparam_gen.go b/protocol/options_credparam_gen.go new file mode 100644 index 0000000..82bd284 --- /dev/null +++ b/protocol/options_credparam_gen.go @@ -0,0 +1,152 @@ +package protocol + +// Code generated by github.com/tinylib/msgp DO NOT EDIT. + +import ( + "github.com/go-webauthn/webauthn/protocol/webauthncose" + "github.com/tinylib/msgp/msgp" +) + +// DecodeMsg implements msgp.Decodable +func (z *CredentialParameter) DecodeMsg(dc *msgp.Reader) (err error) { + var field []byte + _ = field + var zb0001 uint32 + zb0001, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0001 > 0 { + zb0001-- + field, err = dc.ReadMapKeyPtr() + if err != nil { + err = msgp.WrapError(err) + return + } + switch msgp.UnsafeString(field) { + case "type": + { + var zb0002 string + zb0002, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Type") + return + } + z.Type = CredentialType(zb0002) + } + case "alg": + { + var zb0003 int + zb0003, err = dc.ReadInt() + if err != nil { + err = msgp.WrapError(err, "Algorithm") + return + } + z.Algorithm = webauthncose.COSEAlgorithmIdentifier(zb0003) + } + default: + err = dc.Skip() + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + return +} + +// EncodeMsg implements msgp.Encodable +func (z CredentialParameter) EncodeMsg(en *msgp.Writer) (err error) { + // map header, size 2 + // write "type" + err = en.Append(0x82, 0xa4, 0x74, 0x79, 0x70, 0x65) + if err != nil { + return + } + err = en.WriteString(string(z.Type)) + if err != nil { + err = msgp.WrapError(err, "Type") + return + } + // write "alg" + err = en.Append(0xa3, 0x61, 0x6c, 0x67) + if err != nil { + return + } + err = en.WriteInt(int(z.Algorithm)) + if err != nil { + err = msgp.WrapError(err, "Algorithm") + return + } + return +} + +// MarshalMsg implements msgp.Marshaler +func (z CredentialParameter) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + // map header, size 2 + // string "type" + o = append(o, 0x82, 0xa4, 0x74, 0x79, 0x70, 0x65) + o = msgp.AppendString(o, string(z.Type)) + // string "alg" + o = append(o, 0xa3, 0x61, 0x6c, 0x67) + o = msgp.AppendInt(o, int(z.Algorithm)) + return +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *CredentialParameter) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 uint32 + zb0001, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch msgp.UnsafeString(field) { + case "type": + { + var zb0002 string + zb0002, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Type") + return + } + z.Type = CredentialType(zb0002) + } + case "alg": + { + var zb0003 int + zb0003, bts, err = msgp.ReadIntBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Algorithm") + return + } + z.Algorithm = webauthncose.COSEAlgorithmIdentifier(zb0003) + } + default: + bts, err = msgp.Skip(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + o = bts + return +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z CredentialParameter) Msgsize() (s int) { + s = 1 + 5 + msgp.StringPrefixSize + len(string(z.Type)) + 4 + msgp.IntSize + return +} diff --git a/protocol/options_credparam_gen_test.go b/protocol/options_credparam_gen_test.go new file mode 100644 index 0000000..85b59df --- /dev/null +++ b/protocol/options_credparam_gen_test.go @@ -0,0 +1,123 @@ +package protocol + +// Code generated by github.com/tinylib/msgp DO NOT EDIT. + +import ( + "bytes" + "testing" + + "github.com/tinylib/msgp/msgp" +) + +func TestMarshalUnmarshalCredentialParameter(t *testing.T) { + v := CredentialParameter{} + bts, err := v.MarshalMsg(nil) + if err != nil { + t.Fatal(err) + } + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func BenchmarkMarshalMsgCredentialParameter(b *testing.B) { + v := CredentialParameter{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgCredentialParameter(b *testing.B) { + v := CredentialParameter{} + bts := make([]byte, 0, v.Msgsize()) + bts, _ = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts, _ = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalCredentialParameter(b *testing.B) { + v := CredentialParameter{} + bts, _ := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + +func TestEncodeDecodeCredentialParameter(t *testing.T) { + v := CredentialParameter{} + var buf bytes.Buffer + msgp.Encode(&buf, &v) + + m := v.Msgsize() + if buf.Len() > m { + t.Log("WARNING: TestEncodeDecodeCredentialParameter Msgsize() is inaccurate") + } + + vn := CredentialParameter{} + err := msgp.Decode(&buf, &vn) + if err != nil { + t.Error(err) + } + + buf.Reset() + msgp.Encode(&buf, &v) + err = msgp.NewReader(&buf).Skip() + if err != nil { + t.Error(err) + } +} + +func BenchmarkEncodeCredentialParameter(b *testing.B) { + v := CredentialParameter{} + var buf bytes.Buffer + msgp.Encode(&buf, &v) + b.SetBytes(int64(buf.Len())) + en := msgp.NewWriter(msgp.Nowhere) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.EncodeMsg(en) + } + en.Flush() +} + +func BenchmarkDecodeCredentialParameter(b *testing.B) { + v := CredentialParameter{} + var buf bytes.Buffer + msgp.Encode(&buf, &v) + b.SetBytes(int64(buf.Len())) + rd := msgp.NewEndlessReader(buf.Bytes(), b) + dc := msgp.NewReader(rd) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + err := v.DecodeMsg(dc) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/webauthn/session.go b/webauthn/session.go new file mode 100644 index 0000000..6d7f9c6 --- /dev/null +++ b/webauthn/session.go @@ -0,0 +1,25 @@ +package webauthn + +import ( + "time" + + "github.com/go-webauthn/webauthn/protocol" +) + +//go:generate msgp +//msgp:replace protocol.UserVerificationRequirement with:string +//msgp:replace protocol.AuthenticationExtensions with:map[string]any + +// SessionData is the data that should be stored by the Relying Party for the duration of the web authentication +// ceremony. +type SessionData struct { + Challenge string `json:"challenge" msg:"challenge"` + RelyingPartyID string `json:"rpId" msg:"rpid"` + UserID []byte `json:"user_id" msg:"uid"` + AllowedCredentialIDs [][]byte `json:"allowed_credentials,omitempty" msg:"allowed"` + Expires time.Time `json:"expires" msg:"exp"` + + UserVerification protocol.UserVerificationRequirement `json:"userVerification" msg:"uv"` + Extensions protocol.AuthenticationExtensions `json:"extensions,omitempty" msg:"ext"` + CredParams []protocol.CredentialParameter `json:"credParams,omitempty" msg:"params"` +} diff --git a/webauthn/session_gen.go b/webauthn/session_gen.go new file mode 100644 index 0000000..1c4243f --- /dev/null +++ b/webauthn/session_gen.go @@ -0,0 +1,452 @@ +package webauthn + +// Code generated by github.com/tinylib/msgp DO NOT EDIT. + +import ( + "github.com/go-webauthn/webauthn/protocol" + "github.com/tinylib/msgp/msgp" +) + +// DecodeMsg implements msgp.Decodable +func (z *SessionData) DecodeMsg(dc *msgp.Reader) (err error) { + var field []byte + _ = field + var zb0001 uint32 + zb0001, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0001 > 0 { + zb0001-- + field, err = dc.ReadMapKeyPtr() + if err != nil { + err = msgp.WrapError(err) + return + } + switch msgp.UnsafeString(field) { + case "challenge": + z.Challenge, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Challenge") + return + } + case "rpid": + z.RelyingPartyID, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "RelyingPartyID") + return + } + case "uid": + z.UserID, err = dc.ReadBytes(z.UserID) + if err != nil { + err = msgp.WrapError(err, "UserID") + return + } + case "allowed": + var zb0002 uint32 + zb0002, err = dc.ReadArrayHeader() + if err != nil { + err = msgp.WrapError(err, "AllowedCredentialIDs") + return + } + if cap(z.AllowedCredentialIDs) >= int(zb0002) { + z.AllowedCredentialIDs = (z.AllowedCredentialIDs)[:zb0002] + } else { + z.AllowedCredentialIDs = make([][]byte, zb0002) + } + for za0003 := range z.AllowedCredentialIDs { + z.AllowedCredentialIDs[za0003], err = dc.ReadBytes(z.AllowedCredentialIDs[za0003]) + if err != nil { + err = msgp.WrapError(err, "AllowedCredentialIDs", za0003) + return + } + } + case "exp": + z.Expires, err = dc.ReadTime() + if err != nil { + err = msgp.WrapError(err, "Expires") + return + } + case "uv": + { + var zb0003 string + zb0003, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "UserVerification") + return + } + z.UserVerification = protocol.UserVerificationRequirement(zb0003) + } + case "ext": + var zb0004 uint32 + zb0004, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err, "Extensions") + return + } + if z.Extensions == nil { + z.Extensions = make(map[string]interface{}, zb0004) + } else if len(z.Extensions) > 0 { + for key := range z.Extensions { + delete(z.Extensions, key) + } + } + for zb0004 > 0 { + zb0004-- + var za0004 string + var za0005 interface{} + za0004, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Extensions") + return + } + za0005, err = dc.ReadIntf() + if err != nil { + err = msgp.WrapError(err, "Extensions", za0004) + return + } + z.Extensions[za0004] = za0005 + } + case "params": + var zb0005 uint32 + zb0005, err = dc.ReadArrayHeader() + if err != nil { + err = msgp.WrapError(err, "CredParams") + return + } + if cap(z.CredParams) >= int(zb0005) { + z.CredParams = (z.CredParams)[:zb0005] + } else { + z.CredParams = make([]protocol.CredentialParameter, zb0005) + } + for za0006 := range z.CredParams { + err = z.CredParams[za0006].DecodeMsg(dc) + if err != nil { + err = msgp.WrapError(err, "CredParams", za0006) + return + } + } + default: + err = dc.Skip() + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + return +} + +// EncodeMsg implements msgp.Encodable +func (z *SessionData) EncodeMsg(en *msgp.Writer) (err error) { + // map header, size 8 + // write "challenge" + err = en.Append(0x88, 0xa9, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65) + if err != nil { + return + } + err = en.WriteString(z.Challenge) + if err != nil { + err = msgp.WrapError(err, "Challenge") + return + } + // write "rpid" + err = en.Append(0xa4, 0x72, 0x70, 0x69, 0x64) + if err != nil { + return + } + err = en.WriteString(z.RelyingPartyID) + if err != nil { + err = msgp.WrapError(err, "RelyingPartyID") + return + } + // write "uid" + err = en.Append(0xa3, 0x75, 0x69, 0x64) + if err != nil { + return + } + err = en.WriteBytes(z.UserID) + if err != nil { + err = msgp.WrapError(err, "UserID") + return + } + // write "allowed" + err = en.Append(0xa7, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64) + if err != nil { + return + } + err = en.WriteArrayHeader(uint32(len(z.AllowedCredentialIDs))) + if err != nil { + err = msgp.WrapError(err, "AllowedCredentialIDs") + return + } + for za0003 := range z.AllowedCredentialIDs { + err = en.WriteBytes(z.AllowedCredentialIDs[za0003]) + if err != nil { + err = msgp.WrapError(err, "AllowedCredentialIDs", za0003) + return + } + } + // write "exp" + err = en.Append(0xa3, 0x65, 0x78, 0x70) + if err != nil { + return + } + err = en.WriteTime(z.Expires) + if err != nil { + err = msgp.WrapError(err, "Expires") + return + } + // write "uv" + err = en.Append(0xa2, 0x75, 0x76) + if err != nil { + return + } + err = en.WriteString(string(z.UserVerification)) + if err != nil { + err = msgp.WrapError(err, "UserVerification") + return + } + // write "ext" + err = en.Append(0xa3, 0x65, 0x78, 0x74) + if err != nil { + return + } + err = en.WriteMapHeader(uint32(len(z.Extensions))) + if err != nil { + err = msgp.WrapError(err, "Extensions") + return + } + for za0004, za0005 := range z.Extensions { + err = en.WriteString(za0004) + if err != nil { + err = msgp.WrapError(err, "Extensions") + return + } + err = en.WriteIntf(za0005) + if err != nil { + err = msgp.WrapError(err, "Extensions", za0004) + return + } + } + // write "params" + err = en.Append(0xa6, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73) + if err != nil { + return + } + err = en.WriteArrayHeader(uint32(len(z.CredParams))) + if err != nil { + err = msgp.WrapError(err, "CredParams") + return + } + for za0006 := range z.CredParams { + err = z.CredParams[za0006].EncodeMsg(en) + if err != nil { + err = msgp.WrapError(err, "CredParams", za0006) + return + } + } + return +} + +// MarshalMsg implements msgp.Marshaler +func (z *SessionData) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + // map header, size 8 + // string "challenge" + o = append(o, 0x88, 0xa9, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65) + o = msgp.AppendString(o, z.Challenge) + // string "rpid" + o = append(o, 0xa4, 0x72, 0x70, 0x69, 0x64) + o = msgp.AppendString(o, z.RelyingPartyID) + // string "uid" + o = append(o, 0xa3, 0x75, 0x69, 0x64) + o = msgp.AppendBytes(o, z.UserID) + // string "allowed" + o = append(o, 0xa7, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64) + o = msgp.AppendArrayHeader(o, uint32(len(z.AllowedCredentialIDs))) + for za0003 := range z.AllowedCredentialIDs { + o = msgp.AppendBytes(o, z.AllowedCredentialIDs[za0003]) + } + // string "exp" + o = append(o, 0xa3, 0x65, 0x78, 0x70) + o = msgp.AppendTime(o, z.Expires) + // string "uv" + o = append(o, 0xa2, 0x75, 0x76) + o = msgp.AppendString(o, string(z.UserVerification)) + // string "ext" + o = append(o, 0xa3, 0x65, 0x78, 0x74) + o = msgp.AppendMapHeader(o, uint32(len(z.Extensions))) + for za0004, za0005 := range z.Extensions { + o = msgp.AppendString(o, za0004) + o, err = msgp.AppendIntf(o, za0005) + if err != nil { + err = msgp.WrapError(err, "Extensions", za0004) + return + } + } + // string "params" + o = append(o, 0xa6, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73) + o = msgp.AppendArrayHeader(o, uint32(len(z.CredParams))) + for za0006 := range z.CredParams { + o, err = z.CredParams[za0006].MarshalMsg(o) + if err != nil { + err = msgp.WrapError(err, "CredParams", za0006) + return + } + } + return +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *SessionData) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 uint32 + zb0001, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch msgp.UnsafeString(field) { + case "challenge": + z.Challenge, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Challenge") + return + } + case "rpid": + z.RelyingPartyID, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "RelyingPartyID") + return + } + case "uid": + z.UserID, bts, err = msgp.ReadBytesBytes(bts, z.UserID) + if err != nil { + err = msgp.WrapError(err, "UserID") + return + } + case "allowed": + var zb0002 uint32 + zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "AllowedCredentialIDs") + return + } + if cap(z.AllowedCredentialIDs) >= int(zb0002) { + z.AllowedCredentialIDs = (z.AllowedCredentialIDs)[:zb0002] + } else { + z.AllowedCredentialIDs = make([][]byte, zb0002) + } + for za0003 := range z.AllowedCredentialIDs { + z.AllowedCredentialIDs[za0003], bts, err = msgp.ReadBytesBytes(bts, z.AllowedCredentialIDs[za0003]) + if err != nil { + err = msgp.WrapError(err, "AllowedCredentialIDs", za0003) + return + } + } + case "exp": + z.Expires, bts, err = msgp.ReadTimeBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Expires") + return + } + case "uv": + { + var zb0003 string + zb0003, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "UserVerification") + return + } + z.UserVerification = protocol.UserVerificationRequirement(zb0003) + } + case "ext": + var zb0004 uint32 + zb0004, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Extensions") + return + } + if z.Extensions == nil { + z.Extensions = make(map[string]interface{}, zb0004) + } else if len(z.Extensions) > 0 { + for key := range z.Extensions { + delete(z.Extensions, key) + } + } + for zb0004 > 0 { + var za0004 string + var za0005 interface{} + zb0004-- + za0004, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Extensions") + return + } + za0005, bts, err = msgp.ReadIntfBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Extensions", za0004) + return + } + z.Extensions[za0004] = za0005 + } + case "params": + var zb0005 uint32 + zb0005, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "CredParams") + return + } + if cap(z.CredParams) >= int(zb0005) { + z.CredParams = (z.CredParams)[:zb0005] + } else { + z.CredParams = make([]protocol.CredentialParameter, zb0005) + } + for za0006 := range z.CredParams { + bts, err = z.CredParams[za0006].UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "CredParams", za0006) + return + } + } + default: + bts, err = msgp.Skip(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + o = bts + return +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *SessionData) Msgsize() (s int) { + s = 1 + 10 + msgp.StringPrefixSize + len(z.Challenge) + 5 + msgp.StringPrefixSize + len(z.RelyingPartyID) + 4 + msgp.BytesPrefixSize + len(z.UserID) + 8 + msgp.ArrayHeaderSize + for za0003 := range z.AllowedCredentialIDs { + s += msgp.BytesPrefixSize + len(z.AllowedCredentialIDs[za0003]) + } + s += 4 + msgp.TimeSize + 3 + msgp.StringPrefixSize + len(string(z.UserVerification)) + 4 + msgp.MapHeaderSize + if z.Extensions != nil { + for za0004, za0005 := range z.Extensions { + _ = za0005 + s += msgp.StringPrefixSize + len(za0004) + msgp.GuessSize(za0005) + } + } + s += 7 + msgp.ArrayHeaderSize + for za0006 := range z.CredParams { + s += z.CredParams[za0006].Msgsize() + } + return +} diff --git a/webauthn/session_gen_test.go b/webauthn/session_gen_test.go new file mode 100644 index 0000000..53439c8 --- /dev/null +++ b/webauthn/session_gen_test.go @@ -0,0 +1,123 @@ +package webauthn + +// Code generated by github.com/tinylib/msgp DO NOT EDIT. + +import ( + "bytes" + "testing" + + "github.com/tinylib/msgp/msgp" +) + +func TestMarshalUnmarshalSessionData(t *testing.T) { + v := SessionData{} + bts, err := v.MarshalMsg(nil) + if err != nil { + t.Fatal(err) + } + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func BenchmarkMarshalMsgSessionData(b *testing.B) { + v := SessionData{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgSessionData(b *testing.B) { + v := SessionData{} + bts := make([]byte, 0, v.Msgsize()) + bts, _ = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts, _ = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalSessionData(b *testing.B) { + v := SessionData{} + bts, _ := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + +func TestEncodeDecodeSessionData(t *testing.T) { + v := SessionData{} + var buf bytes.Buffer + msgp.Encode(&buf, &v) + + m := v.Msgsize() + if buf.Len() > m { + t.Log("WARNING: TestEncodeDecodeSessionData Msgsize() is inaccurate") + } + + vn := SessionData{} + err := msgp.Decode(&buf, &vn) + if err != nil { + t.Error(err) + } + + buf.Reset() + msgp.Encode(&buf, &v) + err = msgp.NewReader(&buf).Skip() + if err != nil { + t.Error(err) + } +} + +func BenchmarkEncodeSessionData(b *testing.B) { + v := SessionData{} + var buf bytes.Buffer + msgp.Encode(&buf, &v) + b.SetBytes(int64(buf.Len())) + en := msgp.NewWriter(msgp.Nowhere) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.EncodeMsg(en) + } + en.Flush() +} + +func BenchmarkDecodeSessionData(b *testing.B) { + v := SessionData{} + var buf bytes.Buffer + msgp.Encode(&buf, &v) + b.SetBytes(int64(buf.Len())) + rd := msgp.NewEndlessReader(buf.Bytes(), b) + dc := msgp.NewReader(rd) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + err := v.DecodeMsg(dc) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/webauthn/types.go b/webauthn/types.go index 9e8ee8e..247a7e3 100644 --- a/webauthn/types.go +++ b/webauthn/types.go @@ -199,17 +199,3 @@ type User interface { // WebAuthnCredentials provides the list of Credential objects owned by the user. WebAuthnCredentials() []Credential } - -// SessionData is the data that should be stored by the Relying Party for the duration of the web authentication -// ceremony. -type SessionData struct { - Challenge string `json:"challenge"` - RelyingPartyID string `json:"rpId"` - UserID []byte `json:"user_id"` - AllowedCredentialIDs [][]byte `json:"allowed_credentials,omitempty"` - Expires time.Time `json:"expires"` - - UserVerification protocol.UserVerificationRequirement `json:"userVerification"` - Extensions protocol.AuthenticationExtensions `json:"extensions,omitempty"` - CredParams []protocol.CredentialParameter `json:"credParams,omitempty"` -}