From d40d73a3a5e2e91e01c960c4c6c7cf6df7d93ae1 Mon Sep 17 00:00:00 2001 From: pavelbannov Date: Thu, 24 Feb 2022 17:36:44 +0300 Subject: [PATCH] GoogleAuth: fix 94 issue --- .../packages/GoogleAuthenticator.2.4.1.nupkg | Bin 0 -> 17781 bytes .../AuthCodeTest.cs | 43 +++ .../GeneratePinTests.cs | 33 ++ .../Google.Authenticator.Tests.csproj | 29 ++ .../Google.Authenticator.Tests/QRCodeTest.cs | 64 ++++ .../SetupCodeTests.cs | 30 ++ .../ValidationTests.cs | 45 +++ .../Default.aspx | 30 ++ .../Default.aspx.cs | 48 +++ .../Default.aspx.designer.cs | 78 +++++ .../Global.asax | 1 + .../Global.asax.cs | 16 + .../Google.Authenticator.WebSample.csproj | 133 ++++++++ .../Properties/AssemblyInfo.cs | 35 ++ .../Web.Debug.config | 30 ++ .../Web.Release.config | 31 ++ .../Google.Authenticator.WebSample/Web.config | 19 ++ .../Google.Authenticator.WinTest/App.config | 6 + .../Form1.Designer.cs | 208 ++++++++++++ .../Google.Authenticator.WinTest/Form1.cs | 60 ++++ .../Google.Authenticator.WinTest/Form1.resx | 120 +++++++ .../Google.Authenticator.WinTest.csproj | 100 ++++++ .../Google.Authenticator.WinTest/Program.cs | 22 ++ .../Properties/AssemblyInfo.cs | 36 ++ .../Properties/Resources.Designer.cs | 63 ++++ .../Properties/Resources.resx | 117 +++++++ .../Properties/Settings.Designer.cs | 26 ++ .../Properties/Settings.settings | 7 + .../Google.Authenticator.sln | 43 +++ .../Google.Authenticator/Base32Encoding.cs | 140 ++++++++ .../Google.Authenticator.csproj | 38 +++ .../MissingDependencyException.cs | 15 + .../Google.Authenticator/QRException.cs | 13 + .../Google.Authenticator/SetupCode.cs | 21 ++ .../TwoFactorAuthenticator.cs | 321 ++++++++++++++++++ thirdparty/Google.Authenticator/LICENSE | 201 +++++++++++ thirdparty/Google.Authenticator/README.md | 43 +++ web/ASC.Web.Core/ASC.Web.Core.csproj | 2 +- 38 files changed, 2266 insertions(+), 1 deletion(-) create mode 100644 .nuget/packages/GoogleAuthenticator.2.4.1.nupkg create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.Tests/AuthCodeTest.cs create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.Tests/GeneratePinTests.cs create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.Tests/Google.Authenticator.Tests.csproj create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.Tests/QRCodeTest.cs create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.Tests/SetupCodeTests.cs create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.Tests/ValidationTests.cs create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Default.aspx create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Default.aspx.cs create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Default.aspx.designer.cs create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Global.asax create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Global.asax.cs create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Google.Authenticator.WebSample.csproj create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Properties/AssemblyInfo.cs create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Web.Debug.config create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Web.Release.config create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Web.config create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WinTest/App.config create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Form1.Designer.cs create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Form1.cs create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Form1.resx create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Google.Authenticator.WinTest.csproj create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Program.cs create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/AssemblyInfo.cs create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/Resources.Designer.cs create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/Resources.resx create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/Settings.Designer.cs create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/Settings.settings create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator.sln create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator/Base32Encoding.cs create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator/Google.Authenticator.csproj create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator/MissingDependencyException.cs create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator/QRException.cs create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator/SetupCode.cs create mode 100644 thirdparty/Google.Authenticator/Google.Authenticator/TwoFactorAuthenticator.cs create mode 100644 thirdparty/Google.Authenticator/LICENSE create mode 100644 thirdparty/Google.Authenticator/README.md diff --git a/.nuget/packages/GoogleAuthenticator.2.4.1.nupkg b/.nuget/packages/GoogleAuthenticator.2.4.1.nupkg new file mode 100644 index 0000000000000000000000000000000000000000..0cbacb4ac06fcf2ddec60fb9a51519db00ae1026 GIT binary patch literal 17781 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%0AE^6k)z->_z?nKsD`Zk zOn$_#i4G@1HP2I@nzu!<$lc(#tv?|9FQ7fYEyPE+rTNpn(gl2xv%{Q{@8Xd72qKhc zTVvBH^48LScM#70c!#<7*q=N<#$?Oe|Iw0b=&L{)6_u-(B+Z$cie(}5TJlbM>4>!Q z7S&nryu7s5JsTC3%5_SQj<%dx^qd`Vt3SX=af;>A#xa<4|JotM6zz(WX8cjtwDNa2 z@stU$v`C01si#BaoX6MHv#mABr@%ko27IRzLF^)_vVh`%Qx6`alsfV)91YT4YJ>E! zAcS4QLuRNYgt_VdC&2pB^eU$8f}ixq)QcakKfO}DAFF}U372x}X^UYieWn;ioi4MS zg8C~j@exM%ChES)jKEn@a?vaFGCP&($IlC82OKcGv6)9IYv}V>410@vCVX)Z)f3)V z#Oezo7s+2JU*HRKz`tyMS?-j4t6Av`^h$}X%H{z-1|4vE)qb(yvBv|lf$tMo0S~RF zmgv#>#x`B1uq3s$3iMs&#L2Xo=wK4_l(Jf$vseZDD+SZE+DpI_FNvMnoznJGQdBW7 zLWY88jPlgawEB+n{4$=X9k48>I~BTR)GND*Y^8_yQ}2}N@=5NL?$f(P@&oBWIkBGV zJT?3RJ3wCZFRCwn=AIJq^l3`Iu#@kTKPnV60vh@u8d^|UCHD14CHQr_673e+Li^cJ z@&kn;C@=Y+DWpG{p@?Azngg@xKc;rf^4FHMA;0=+%Bmua12HS09jV*^*c4!!V$A{T zOWOe6XW{|h=l+1A6KS1^2kPsWC(EDNbwr%$d(NbqVvEU1P%IG8a1Tyw}@AXE}eaWA3}cLEssi zvR+|b-eK=22{Zc9>a7K1O|RjV6E@*t z<~)1(Qm-7Hxw&^u^7~S-o1WXCRaQPZ-O!+1{CoGE%9i`>V;9|eUn<_*CvPXYvo$?- z?68u=HEF?Ozu|2P$5}{fo0SXjfCFIhQ^CISi@u z^5xFHp8L6*X7jWM@tqqLeZL$p&znt3Z+*0h0(S$hbnc5OPRuh-Kuf?Tm$3=)`6URG z5Pk1}j6D6xySY#0OQE+IdvHKL!N36ABHzp#1b)Ph zJ|8lKf8yab^TI5aR6cX&NU%g=t3mTuheT2}x!gdFHn0^LpMkUPeQ1-MZ&FEqph^@Gl(7p)^CvI4>UQ z@#{m4mi-&SqUlR$7MbR*o_V~iY1x>QpU{)UmgtC=?#s%roa4Q&oj&|C`yl9v8`GJ= zAIeOPgEHrO%2nf&`BQQ--AZN@v%8-28!LUZLBQel+~- zxzw~^H;TG^ZCu--+biqK&G?p}ZO1YvH!ABb{E@LV*^(ZX4phV6;e zZls@?s1~5>oY|)W3`ijn9t66bkmIwT$;wXCgML77i6I-_O_w|mT7!E@l##zU`(qnG`G8)3^@bel|YGPa=C=1o1DSyC!)%?$|)VAi3o(oyZyw zp84D}%ATHZXU%?S;6t}ehi9HR`GY(~Z1PidV$odZl){?x!z-^_Jf;qoW_hEQ%s1Pi zM-*@7Pv%mc3#s_`lbe?5CS-9OIcX&3IbV1gd05T6|0J9!cjQh_&~tF^5d+fw(0A`%`J4-oJ5 zhBtZEFvSl&z)v>;TDyluI&fQrL=EPO{mzwm?PLbc`?u4{%$2|qrYBOH`{(LVewDKF z=gxXlufQ-N@hsxSnv*y6p@?N%9&A`fgoMI=jAkB8JPCL?X@;o3!d(M&nnd3+#$vcqG_&?hobMnEXuR%;bbx;#We?XZm^jBrB7dLdO&$ay#+euRym%mrdvj_!U9K|VR$id z^g}I+d~h3pW~1i8jRoy{SK{`YJ-CHaGB$uY-c0cG7Gc)UL4i9r2nb#;nrCCa*fhMh zL`o_n3nw;9QWAn+?QA zh}&*z_rCNKsgNW~CRJB*k|QVO3;i@ys2pIv@Q2{F4~7LN)1rBtaKnvj78t%4H!?Py z0BU#$@OVQ%-zne@%{tviT|S%!EFXZZfDm+6`b%UWZP%v;6d+j=%Br z{Mz;V4IcLw(GpUmVHJde8|#s?m|^3ln#EYHw(dHO#3xm>-p z6Wtq`Su*-w#X#KnU+XPibx$c1EwwFT6iMu~QN!moS7^kXUc;vR5raTG9Ew8*At)IH z8=x7#O#(1x-j7E}s37$3pUy~3YpVGH{B}=GS(y-`HMRMl&ZbD|IA&vH2$t%~BNR;8 zh=MGsnjH~KswT$i#ehRC9%>i^;Y89!$uz_Tm^WE2)p-GgNX#+%u{{D9D>7q)#7Ov2 z@Pi;sw+_3gsgT6HQ4tbfr0Jf6#G0BMWn%?uY*a->>?M*Y04?S#)li3Z_51vLYO{Vy zVPvgT1_X`%!{)gMnyN<#ho-z9|7Y2|dhxnO{?Fk9|F_?`c5>D?b$k)#BRPoShzf8W z<^$nsMb8Y)SPVD~Z505OSS6LcT@r{39DdqtStLaBFim_t;TzMt7cLGK*F(AB3jXrg|;xS5>P` zy5vE8rU+LE7==R+g;kQ#780Gdlz-jTvfs^95fywv#TNrXUiKzQ`voWynUCh_aG_R8 z;Gbe%IO}4Ndl+ip-E(ebS%m~`WzS%!zgvy~6snXc*JIVU9m(=IPCj(a6jcdy&`3p& zp<=;a)QuGlQU!ZfP;nojFdNc0^KqS)?qrD~57`cKxl?8eHD_i>mMqSibiYXF3f3e; zOmY3oLkL>Rvz2Ko(2ChYm1`>ARi%YbD;nD3U#hvdwY0UiwU*tRcDFYd7MDAoRleoB zNqt?Vrz+lE#m%`E*+Q=UW70)0P_>$kE+nzX=Cr>`H)ogP`+`#oguk0twjKDF3Yt-r>sk_&M*FGhN4_E!Xoo8k2{qk#ifD$WBXgC${*?eMKr&418k$w}|5As=S!(58pEw zsqX)JareE0&A zwzC1uApmqed8@g{d4L>WNzk=HXZcvEtn;}Zx18Hw>4PX2q*ok`Rk>;IfH~7bAWaL| zPGgvefbWZHqfmM*rMz?dBv+mchPuWL9WE*uXx={2}3yMDL@ z?I+stCKXj$Y^=S3;y0SM-z`VtET>iATy`m~T|k|hh2_{Tk^QRy?=e^jefrE)YhU^^&#zj*0lJ*HcAiCf^l zBsakxyd|*o(rN%rQZjh{p3z5__Pk44ZAfP{J~Xo@2_O3W`)%Fc@fNaA+v~e(zm>SZ z-w%ELe>M>w95qzVW=AO?K>z?kAprpXTL4O;f+Dh_bhaj~YTI_(VhFzH>N2PWsIZYO zxnO`s7?w$7VTvk^wB^1GQC5<16`|?fH+nLa`umxsG@7v4;UKK-7VbN_>+WtBtb#hzdq4QasPD1gt>&r!M~LhX9<6DamDAvA z`rN|0S3C<|a_%3c*41Osup`>=ZF}#En%e|ez8<4pXI3=$B*xJs)h{Y2Ul$^NuCLya zqUB^qSN)WH&u@OTVSWhoW8Nr^6E|q8L@!$>9P1bH7`at<-DHuDseN8caW5Kcr(iB+ zsjp*2q+sQ>stu<)d}O@<`Mt@^F}e0{GEtUEJVYoHOY7LDCT`%IAaXw}>FM~uVg^pE zTGs2CN)k^~x*e&lb!P4kEA8fqMRpoCbBnkO6|L%lb+`fvcuSg-ldQba`(q~Uy{Fn3{1>&+F#egxM>W>{Z9^_Ini3TnDH!&$g3FDDN8p+G5uXh|a zXIL~rm2EhVf85f|`s^XCZ_l7wB=UICg(Yy2V)m@?LU()|nS)af@~Vv)|7aP{AwVB0 zXl-M#VKBqz{6hF^PK9UbVwGLF&(I%tW`U4dc?_NG{7po9>;hwcUyLY2}uKzCfcM$eb zvg|IfnfwIY<~)*W#hxR0*6z_(@+ksg;Th+`MEdk>dr3Eg7$B>Sf?xL8en=8(spq+~ zD<}3i%4h5Tq*P~>5q2N5tZ?SH96QMqqzA%(4lfokH4zD*6*`X~^ad!N=Xc4^Sq&)Iuq2z{FfeT4>SQC9pByKY z%XzWCgJU4GT{vm!>>%!lVl5oBp&*V5!!Hk!LblC-P~(QEcCIGI?u-au1^~epT9;>R z9LW`5hwU0hFj?*692yE#nh-gvsuXY_J+0+$?d$itq&k=r0nOdEIdwR)OPzw$r=$PA z2w`SmY&TDc{~i_$c>`l1RpR3xIJLGL3jRo3NwraRVDw+9<84luw%moh+xGi>YT)RE z(*f&iy8&}^!iZEd^!FQ1<<>4Qrw2Rw4D!t;`Ts}L@xN&RptHUU>;L5mx&NjCApf^( z3)|bd{O4lVSMhW(b=IZxu(c^ol9d}~KnQ)4=nX!lmBP^=2uX4S+$lhJ7*8G<;wPRj zp=z(+ZW1aY=)&oJ>$PY8IYS8EGB(U#F2LWCqn1g?m0vDE$ILeG)vUk!yd@{B){^77 z&fNBrJWFHvf)!@aNP_?aFr_-gLgtcGUj7xXr?9!T(ML@thS` zwsai7ADUNwbh2Z>x`%o#(H~yqExdfF<-_*Ydvoq5*Rr67!>&mw_YVNK^#1^cLm)65 z3KEmE6Vp@mi&KlrGLut_^>b585>paO67`ewi&Aw9it-Cmi%K$q5@r_3mWh^@X6BYj z1}3IS<`#zLM#&}ymS%>Qrlv_osd@#)xhcs7xk29jw+saKg}Is0zr$ng(v$TIJ8l@`jXE7eQK>8YpYJV+&_HZ=F{=(w+`fgakSp;6J+Meu(rhO z_FtpSl~VWB`;Y!E-&rEJ{hsLy&*X*`n-Uj3F|rgeU%(D*U#~e`b+5$8^XK}nr^_o3MD=XtRatYSJ1d|?j!(yJtKiBx z>sedxEfoBu@vWk4)@LbC4acVW*3#zA*`9C26vB%XZMll}MN}qE-Mpbxr1{w&wp~G@ zY%fcl3p9?W?7|LW^av!qPO1C2hnAv$wYlz&{3%Cgnd z-S(ta^5C|+lDhjJt}Joia$4tZ=*_=(V^-wY9e(pJaqZIbgP|<;r7@1;3BgWQopqOY z9R5+8FnhhKO=V!RPeqI5?<31B|Enk2#Q*zN*A2`zj7%a7xR22QrWquFbf^ZpM)boY z5W1X!=@0i&5-8?iIavTab4 literal 0 HcmV?d00001 diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.Tests/AuthCodeTest.cs b/thirdparty/Google.Authenticator/Google.Authenticator.Tests/AuthCodeTest.cs new file mode 100644 index 0000000000..87f73f3080 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.Tests/AuthCodeTest.cs @@ -0,0 +1,43 @@ +using System.Text; +using Shouldly; +using Xunit; + +namespace Google.Authenticator.Tests +{ + public class AuthCodeTest + { + [Fact] + public void BasicAuthCodeTest() + { + var secretKey = "PJWUMZKAUUFQKJBAMD6VGJ6RULFVW4ZH"; + var expected = "551508"; + + var tfa = new TwoFactorAuthenticator(); + + var currentTime = 1416643820; + + // I actually think you are supposed to divide the time by 30 seconds? + // Maybe need an overload that takes a DateTime? + var actual = tfa.GeneratePINAtInterval(secretKey, currentTime, 6); + + actual.ShouldBe(expected); + } + + [Fact] + public void Base32AuthCodeTest() + { + var secretKey = Base32Encoding.ToString(Encoding.UTF8.GetBytes("PJWUMZKAUUFQKJBAMD6VGJ6RULFVW4ZH")); + var expected = "551508"; + + var tfa = new TwoFactorAuthenticator(); + + var currentTime = 1416643820; + + // I actually think you are supposed to divide the time by 30 seconds? + // Maybe need an overload that takes a DateTime? + var actual = tfa.GeneratePINAtInterval(secretKey, currentTime, 6, true); + + actual.ShouldBe(expected); + } + } +} \ No newline at end of file diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.Tests/GeneratePinTests.cs b/thirdparty/Google.Authenticator/Google.Authenticator.Tests/GeneratePinTests.cs new file mode 100644 index 0000000000..06c9bbab83 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.Tests/GeneratePinTests.cs @@ -0,0 +1,33 @@ +using Xunit; +using Shouldly; +using System.Text; +using System.Net.NetworkInformation; + +namespace Google.Authenticator.Tests +{ + public class GeneratePinTests + { + [Fact] + public void OverloadsReturnSamePIN() + { + var secret = "JBSWY3DPEHPK3PXP"; + var secretAsBytes = Encoding.UTF8.GetBytes(secret); + var secretAsBase32 = Base32Encoding.ToString(secretAsBytes); + long counter = 54615912; + var expected = "508826"; + + var subject = new TwoFactorAuthenticator(); + + var pinFromString = subject.GeneratePINAtInterval(secret, counter); + var pinFromBytes = subject.GeneratePINAtInterval(secretAsBytes, counter); + var pinFromBase32 = subject.GeneratePINAtInterval(secretAsBase32, counter, secretIsBase32: true); + + pinFromString.ShouldBe(expected); + pinFromBytes.ShouldBe(expected); + pinFromBase32.ShouldBe(expected); + } + } +} + +// private long GetCurrentCounter(DateTime now, DateTime epoch, int timeStep) => + //(long) (now - epoch).TotalSeconds / timeStep; \ No newline at end of file diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.Tests/Google.Authenticator.Tests.csproj b/thirdparty/Google.Authenticator/Google.Authenticator.Tests/Google.Authenticator.Tests.csproj new file mode 100644 index 0000000000..5379148e98 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.Tests/Google.Authenticator.Tests.csproj @@ -0,0 +1,29 @@ + + + + netcoreapp3.1;net452;net5.0 + + false + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + \ No newline at end of file diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.Tests/QRCodeTest.cs b/thirdparty/Google.Authenticator/Google.Authenticator.Tests/QRCodeTest.cs new file mode 100644 index 0000000000..1b0972d619 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.Tests/QRCodeTest.cs @@ -0,0 +1,64 @@ +using Xunit; +using Shouldly; +using System.Diagnostics; +using System; +using ZXing; +using System.Collections.Generic; +using System.IO; + +namespace Google.Authenticator.Tests +{ + public class QRCodeTest + { + [Theory] + [InlineData("issuer", "otpauth://totp/issuer:a@b.com?secret=ONSWG4TFOQ&issuer=issuer")] + [InlineData("Foo & Bar", "otpauth://totp/Foo%20%26%20Bar:a@b.com?secret=ONSWG4TFOQ&issuer=Foo%20%26%20Bar")] + [InlineData("个", "otpauth://totp/%E4%B8%AA:a@b.com?secret=ONSWG4TFOQ&issuer=%E4%B8%AA")] + public void CanGenerateQRCode(string issuer, string expectedUrl) + { + var subject = new TwoFactorAuthenticator(); + var setupCodeInfo = subject.GenerateSetupCode( + issuer, + "a@b.com", + "secret", + false, + 2); + + var actualUrl = ExtractUrlFromQRImage(setupCodeInfo.QrCodeSetupImageUrl); + + actualUrl.ShouldBe(expectedUrl); + } + + private static string ExtractUrlFromQRImage(string qrCodeSetupImageUrl) + { + var headerLength = "data:image/png;base64,".Length; + var rawImageData = qrCodeSetupImageUrl.Substring(headerLength, qrCodeSetupImageUrl.Length - headerLength); + var imageData = Convert.FromBase64String(rawImageData); + + //var reader = new BarcodeReaderGeneric(); + //reader.Options.PossibleFormats = new List { + // BarcodeFormat.QR_CODE + //}; + +#if NETFRAMEWORK + var reader = new BarcodeReader(); + reader.Options.PossibleFormats = new List { + BarcodeFormat.QR_CODE + }; + using (var ms = new MemoryStream(imageData)) + { + var image = new System.Drawing.Bitmap(ms); + return reader.Decode(image).Text; + } +#else + var reader = new BarcodeReaderGeneric(); + reader.Options.PossibleFormats = new List { + BarcodeFormat.QR_CODE + }; + var image = new ImageMagick.MagickImage(imageData); + var wrappedImage = new ZXing.Magick.MagickImageLuminanceSource(image); + return reader.Decode(wrappedImage).Text; +#endif + } + } +} \ No newline at end of file diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.Tests/SetupCodeTests.cs b/thirdparty/Google.Authenticator/Google.Authenticator.Tests/SetupCodeTests.cs new file mode 100644 index 0000000000..eec5a6fc5b --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.Tests/SetupCodeTests.cs @@ -0,0 +1,30 @@ +using Xunit; +using Shouldly; +using System.Text; + +namespace Google.Authenticator.Tests +{ + public class SetupCodeTests + { + [Fact] + public void ByteAndStringGeneratesSameSetupCode() + { + var secret = "12345678901234567890123456789012"; + var secretAsByteArray = Encoding.UTF8.GetBytes(secret); + var secretAsBase32 = Base32Encoding.ToString(secretAsByteArray); + var issuer = "Test"; + var accountName = "TestAccount"; + var expected = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA"; + + var subject = new TwoFactorAuthenticator(); + + var setupCodeFromString = subject.GenerateSetupCode(issuer, accountName, secret, false); + var setupCodeFromByteArray = subject.GenerateSetupCode(issuer, accountName, secretAsByteArray, 3, false); + var setupCodeFromBase32 = subject.GenerateSetupCode(issuer, accountName, secretAsBase32, true); + + setupCodeFromString.ManualEntryKey.ShouldBe(expected); + setupCodeFromByteArray.ManualEntryKey.ShouldBe(expected); + setupCodeFromBase32.ManualEntryKey.ShouldBe(expected); + } + } +} \ No newline at end of file diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.Tests/ValidationTests.cs b/thirdparty/Google.Authenticator/Google.Authenticator.Tests/ValidationTests.cs new file mode 100644 index 0000000000..8164183137 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.Tests/ValidationTests.cs @@ -0,0 +1,45 @@ +using Xunit; +using Shouldly; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System; + +namespace Google.Authenticator.Tests +{ + public class ValidationTests + { + const string secret = "ggggjhG&^*&^jfSSSddd"; + private readonly static byte[] secretAsBytes = Encoding.UTF8.GetBytes(secret); + private readonly static string secretAsBase32 = Base32Encoding.ToString(secretAsBytes); + + [Theory] + [MemberData(nameof(GetPins))] + public void ValidateWorksWithDifferentSecretTypes(string pin, int irrelevantNumberToAvoidDuplicatePinsBeingRemoved) + { + // We can't directly test that the different overloads for GetCurrentPIN creates the same result, + // as the time difference may may cause different PINS to be created. + // So instead we generate the PINs by each method and validate each one by each method. + var subject = new TwoFactorAuthenticator(); + + subject.ValidateTwoFactorPIN(secret, pin, false); + subject.ValidateTwoFactorPIN(secret, pin, TimeSpan.FromMinutes(irrelevantNumberToAvoidDuplicatePinsBeingRemoved), false); + subject.ValidateTwoFactorPIN(secretAsBytes, pin); + subject.ValidateTwoFactorPIN(secretAsBytes, pin, TimeSpan.FromMinutes(irrelevantNumberToAvoidDuplicatePinsBeingRemoved)); + subject.ValidateTwoFactorPIN(secretAsBase32, pin, true); + subject.ValidateTwoFactorPIN(secretAsBase32, pin, TimeSpan.FromMinutes(irrelevantNumberToAvoidDuplicatePinsBeingRemoved), true); + } + + public static IEnumerable GetPins() + { + var subject = new TwoFactorAuthenticator(); + + yield return new object[] { subject.GetCurrentPIN(secret), 2 }; + yield return new object[] { subject.GetCurrentPIN(secret, DateTime.UtcNow), 3 }; + yield return new object[] { subject.GetCurrentPIN(secretAsBytes), 4 }; + yield return new object[] { subject.GetCurrentPIN(secretAsBytes, DateTime.UtcNow), 5 }; + yield return new object[] { subject.GetCurrentPIN(secretAsBase32, true), 6 }; + yield return new object[] { subject.GetCurrentPIN(secretAsBase32, DateTime.UtcNow, true), 7 }; + } + } +} \ No newline at end of file diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Default.aspx b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Default.aspx new file mode 100644 index 0000000000..6935444393 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Default.aspx @@ -0,0 +1,30 @@ +<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Google.Authenticator.WebSample.Default" %> + + + + + + Google Authenticator Sample + + + +
+
+ Account Secret Key (randomly generated): +
+ Setup QR Code:
+
+
+ Manual Setup Code: +
+ Validate Code:
+
+
+ + diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Default.aspx.cs b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Default.aspx.cs new file mode 100644 index 0000000000..85b94f4234 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Default.aspx.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; + +namespace Google.Authenticator.WebSample +{ + public partial class Default : System.Web.UI.Page + { + protected void Page_Load(object sender, EventArgs e) + { + if (string.IsNullOrEmpty(Request.QueryString["key"])) + { + Response.Redirect("~/default.aspx?key=" + Guid.NewGuid().ToString().Replace("-", "").Substring(0, 10)); + } + + this.lblSecretKey.Text = Request.QueryString["key"]; + + TwoFactorAuthenticator tfa = new TwoFactorAuthenticator(); + var setupInfo = tfa.GenerateSetupCode("我 & You", "user@example.com", Request.QueryString["key"], false, 10); + + string qrCodeImageUrl = setupInfo.QrCodeSetupImageUrl; + string manualEntrySetupCode = setupInfo.ManualEntryKey; + + this.imgQrCode.ImageUrl = qrCodeImageUrl; + this.lblManualSetupCode.Text = manualEntrySetupCode; + } + + protected void btnValidate_Click(object sender, EventArgs e) + { + TwoFactorAuthenticator tfa = new TwoFactorAuthenticator(); + var result = tfa.ValidateTwoFactorPIN(Request.QueryString["key"], this.txtCode.Text); + + if (result) + { + this.lblValidationResult.Text = this.txtCode.Text + " is a valid PIN at UTC time " + DateTime.UtcNow.ToString(); + this.lblValidationResult.ForeColor = System.Drawing.Color.Green; + } + else + { + this.lblValidationResult.Text = this.txtCode.Text + " is not a valid PIN at UTC time " + DateTime.UtcNow.ToString(); + this.lblValidationResult.ForeColor = System.Drawing.Color.Red; + } + } + } +} diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Default.aspx.designer.cs b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Default.aspx.designer.cs new file mode 100644 index 0000000000..31d808e7b4 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Default.aspx.designer.cs @@ -0,0 +1,78 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Google.Authenticator.WebSample { + + + public partial class Default { + + /// + /// form1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.HtmlControls.HtmlForm form1; + + /// + /// lblSecretKey control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblSecretKey; + + /// + /// imgQrCode control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Image imgQrCode; + + /// + /// lblManualSetupCode control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblManualSetupCode; + + /// + /// txtCode control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtCode; + + /// + /// btnValidate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnValidate; + + /// + /// lblValidationResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblValidationResult; + } +} diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Global.asax b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Global.asax new file mode 100644 index 0000000000..48dd76bb57 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Global.asax @@ -0,0 +1 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="Google.Authenticator.WebSample.Global" Language="C#" %> diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Global.asax.cs b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Global.asax.cs new file mode 100644 index 0000000000..402dd92eb8 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Global.asax.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Security; +using System.Web.SessionState; + +namespace Google.Authenticator.WebSample +{ + public class Global : System.Web.HttpApplication + { + protected void Application_Start(object sender, EventArgs e) + { + } + } +} \ No newline at end of file diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Google.Authenticator.WebSample.csproj b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Google.Authenticator.WebSample.csproj new file mode 100644 index 0000000000..ead60b46bf --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Google.Authenticator.WebSample.csproj @@ -0,0 +1,133 @@ + + + + + Debug + AnyCPU + + + 2.0 + {21A63F79-D85F-4FE7-AC74-5171FAC0DCBF} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Google.Authenticator.WebSample + Google.Authenticator.WebSample + v4.8 + true + + + + + SAK + SAK + SAK + SAK + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + Web.config + + + Web.config + + + + + + + + + + Default.aspx + ASPXCodeBehind + + + Default.aspx + + + Global.asax + + + + + + {3d92de47-0ab8-466f-9083-af65d865e4be} + Google.Authenticator + + + + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + True + True + 11275 + / + http://localhost:11275/ + False + False + + + False + + + + + + \ No newline at end of file diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Properties/AssemblyInfo.cs b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..a514574149 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Google.Authenticator.WebSample")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Google.Authenticator.WebSample")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("59ad50f8-c7b9-4b70-bd7c-c2caa8d8d6b7")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Web.Debug.config b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Web.Debug.config new file mode 100644 index 0000000000..2e302f9f95 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Web.Debug.config @@ -0,0 +1,30 @@ + + + + + + + + + + \ No newline at end of file diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Web.Release.config b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Web.Release.config new file mode 100644 index 0000000000..c35844462b --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Web.Release.config @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Web.config b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Web.config new file mode 100644 index 0000000000..1bce2fb97d --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WebSample/Web.config @@ -0,0 +1,19 @@ + + + + + + + + + \ No newline at end of file diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/App.config b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/App.config new file mode 100644 index 0000000000..5ffd8f8ee2 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Form1.Designer.cs b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Form1.Designer.cs new file mode 100644 index 0000000000..e753e28dca --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Form1.Designer.cs @@ -0,0 +1,208 @@ +namespace Google.Authenticator.WinTest +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.txtAccountTitle = new System.Windows.Forms.TextBox(); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.txtSecretKey = new System.Windows.Forms.TextBox(); + this.pbQR = new System.Windows.Forms.PictureBox(); + this.btnSetup = new System.Windows.Forms.Button(); + this.btnGetCurrentCode = new System.Windows.Forms.Button(); + this.txtSetupCode = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); + this.txtCode = new System.Windows.Forms.TextBox(); + this.btnTest = new System.Windows.Forms.Button(); + this.txtCurrentCodes = new System.Windows.Forms.TextBox(); + this.btnDebugTest = new System.Windows.Forms.Button(); + ((System.ComponentModel.ISupportInitialize)(this.pbQR)).BeginInit(); + this.SuspendLayout(); + // + // txtAccountTitle + // + this.txtAccountTitle.Location = new System.Drawing.Point(92, 15); + this.txtAccountTitle.Name = "txtAccountTitle"; + this.txtAccountTitle.Size = new System.Drawing.Size(155, 21); + this.txtAccountTitle.TabIndex = 0; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(13, 18); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(73, 13); + this.label1.TabIndex = 1; + this.label1.Text = "Account Title:"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(24, 44); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(63, 13); + this.label2.TabIndex = 3; + this.label2.Text = "Secret Key:"; + // + // txtSecretKey + // + this.txtSecretKey.Location = new System.Drawing.Point(92, 41); + this.txtSecretKey.Name = "txtSecretKey"; + this.txtSecretKey.Size = new System.Drawing.Size(155, 21); + this.txtSecretKey.TabIndex = 2; + // + // pbQR + // + this.pbQR.BackColor = System.Drawing.Color.White; + this.pbQR.Location = new System.Drawing.Point(34, 67); + this.pbQR.Name = "pbQR"; + this.pbQR.Size = new System.Drawing.Size(231, 223); + this.pbQR.TabIndex = 4; + this.pbQR.TabStop = false; + // + // btnSetup + // + this.btnSetup.Location = new System.Drawing.Point(337, 15); + this.btnSetup.Name = "btnSetup"; + this.btnSetup.Size = new System.Drawing.Size(243, 46); + this.btnSetup.TabIndex = 5; + this.btnSetup.Text = "Generate Setup / Get QR Code"; + this.btnSetup.UseVisualStyleBackColor = true; + this.btnSetup.Click += new System.EventHandler(this.btnSetup_Click); + // + // btnGetCurrentCode + // + this.btnGetCurrentCode.Location = new System.Drawing.Point(396, 250); + this.btnGetCurrentCode.Name = "btnGetCurrentCode"; + this.btnGetCurrentCode.Size = new System.Drawing.Size(173, 27); + this.btnGetCurrentCode.TabIndex = 6; + this.btnGetCurrentCode.Text = "Get Current"; + this.btnGetCurrentCode.UseVisualStyleBackColor = true; + this.btnGetCurrentCode.Click += new System.EventHandler(this.btnGetCurrentCode_Click); + // + // txtSetupCode + // + this.txtSetupCode.Location = new System.Drawing.Point(337, 67); + this.txtSetupCode.Multiline = true; + this.txtSetupCode.Name = "txtSetupCode"; + this.txtSetupCode.ReadOnly = true; + this.txtSetupCode.Size = new System.Drawing.Size(243, 105); + this.txtSetupCode.TabIndex = 7; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(330, 182); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(60, 13); + this.label3.TabIndex = 8; + this.label3.Text = "Test Code:"; + // + // txtCode + // + this.txtCode.Location = new System.Drawing.Point(396, 179); + this.txtCode.Name = "txtCode"; + this.txtCode.Size = new System.Drawing.Size(124, 21); + this.txtCode.TabIndex = 9; + // + // btnTest + // + this.btnTest.Location = new System.Drawing.Point(396, 217); + this.btnTest.Name = "btnTest"; + this.btnTest.Size = new System.Drawing.Size(173, 27); + this.btnTest.TabIndex = 10; + this.btnTest.Text = "Test Two-Factor Code"; + this.btnTest.UseVisualStyleBackColor = true; + this.btnTest.Click += new System.EventHandler(this.btnTest_Click); + // + // txtCurrentCodes + // + this.txtCurrentCodes.Location = new System.Drawing.Point(337, 283); + this.txtCurrentCodes.Multiline = true; + this.txtCurrentCodes.Name = "txtCurrentCodes"; + this.txtCurrentCodes.ReadOnly = true; + this.txtCurrentCodes.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.txtCurrentCodes.Size = new System.Drawing.Size(243, 105); + this.txtCurrentCodes.TabIndex = 11; + // + // btnDebugTest + // + this.btnDebugTest.Location = new System.Drawing.Point(27, 361); + this.btnDebugTest.Name = "btnDebugTest"; + this.btnDebugTest.Size = new System.Drawing.Size(173, 27); + this.btnDebugTest.TabIndex = 12; + this.btnDebugTest.Text = "Misc Test"; + this.btnDebugTest.UseVisualStyleBackColor = true; + this.btnDebugTest.Click += new System.EventHandler(this.btnDebugTest_Click); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(607, 408); + this.Controls.Add(this.btnDebugTest); + this.Controls.Add(this.txtCurrentCodes); + this.Controls.Add(this.btnTest); + this.Controls.Add(this.txtCode); + this.Controls.Add(this.label3); + this.Controls.Add(this.txtSetupCode); + this.Controls.Add(this.btnGetCurrentCode); + this.Controls.Add(this.btnSetup); + this.Controls.Add(this.pbQR); + this.Controls.Add(this.label2); + this.Controls.Add(this.txtSecretKey); + this.Controls.Add(this.label1); + this.Controls.Add(this.txtAccountTitle); + this.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.Name = "Form1"; + this.Text = "Google Authenticator Test App"; + this.Load += new System.EventHandler(this.Form1_Load); + ((System.ComponentModel.ISupportInitialize)(this.pbQR)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox txtAccountTitle; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TextBox txtSecretKey; + private System.Windows.Forms.PictureBox pbQR; + private System.Windows.Forms.Button btnSetup; + private System.Windows.Forms.Button btnGetCurrentCode; + private System.Windows.Forms.TextBox txtSetupCode; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.TextBox txtCode; + private System.Windows.Forms.Button btnTest; + private System.Windows.Forms.TextBox txtCurrentCodes; + private System.Windows.Forms.Button btnDebugTest; + } +} + diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Form1.cs b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Form1.cs new file mode 100644 index 0000000000..58b707dee1 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Form1.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Google.Authenticator.WinTest +{ + public partial class Form1 : Form + { + public Form1() + { + InitializeComponent(); + } + + private void Form1_Load(object sender, EventArgs e) + { + this.txtAccountTitle.Text = "QRTestAccount"; + this.txtSecretKey.Text = "f68f1fe894d548a1bbc66165c46e61eb"; //Guid.NewGuid().ToString().Replace("-", ""); + } + + private void btnSetup_Click(object sender, EventArgs e) + { + TwoFactorAuthenticator tfA = new TwoFactorAuthenticator(); + var setupCode = tfA.GenerateSetupCode(this.txtAccountTitle.Text, this.txtAccountTitle.Text, this.txtSecretKey.Text, false, 3); + + //WebClient wc = new WebClient(); + using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(setupCode.QrCodeSetupImageUrl.Replace("data:image/png;base64,", "")))) + this.pbQR.Image = Image.FromStream(ms); + + this.txtSetupCode.Text = "Account: " + setupCode.Account + System.Environment.NewLine + + "Secret Key: " + this.txtSecretKey.Text + System.Environment.NewLine + + "Encoded Key: " + setupCode.ManualEntryKey; + } + + private void btnTest_Click(object sender, EventArgs e) + { + TwoFactorAuthenticator tfA = new TwoFactorAuthenticator(); + var result = tfA.ValidateTwoFactorPIN(txtSecretKey.Text, this.txtCode.Text); + + MessageBox.Show(result ? "Validated!" : "Incorrect", "Result"); + } + + private void btnGetCurrentCode_Click(object sender, EventArgs e) + { + this.txtCurrentCodes.Text = string.Join(System.Environment.NewLine, new TwoFactorAuthenticator().GetCurrentPINs(this.txtSecretKey.Text)); + } + + private void btnDebugTest_Click(object sender, EventArgs e) + { + + } + } +} diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Form1.resx b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Form1.resx new file mode 100644 index 0000000000..29dcb1b3a3 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Form1.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Google.Authenticator.WinTest.csproj b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Google.Authenticator.WinTest.csproj new file mode 100644 index 0000000000..944c554cbb --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Google.Authenticator.WinTest.csproj @@ -0,0 +1,100 @@ + + + + + Debug + AnyCPU + {C2B44C17-B77B-4DA8-B924-96B28B50D198} + WinExe + Properties + Google.Authenticator.WinTest + Google.Authenticator.WinTest + v4.8 + 512 + SAK + SAK + SAK + SAK + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + Form + + + Form1.cs + + + + + Form1.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + {3d92de47-0ab8-466f-9083-af65d865e4be} + Google.Authenticator + + + + + \ No newline at end of file diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Program.cs b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Program.cs new file mode 100644 index 0000000000..0ae3ec9097 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Google.Authenticator.WinTest +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + } +} diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/AssemblyInfo.cs b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..97a0b8949a --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Google.Authenticator.WinTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Google.Authenticator.WinTest")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("6d26d66d-66b3-43c4-b1bd-73e6594aec0d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/Resources.Designer.cs b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..1647eb98c0 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Google.Authenticator.WinTest.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Google.Authenticator.WinTest.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/Resources.resx b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/Resources.resx new file mode 100644 index 0000000000..af7dbebbac --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/Settings.Designer.cs b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/Settings.Designer.cs new file mode 100644 index 0000000000..77d95c87e1 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Google.Authenticator.WinTest.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.1.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/Settings.settings b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/Settings.settings new file mode 100644 index 0000000000..39645652af --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.WinTest/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/thirdparty/Google.Authenticator/Google.Authenticator.sln b/thirdparty/Google.Authenticator/Google.Authenticator.sln new file mode 100644 index 0000000000..ebc93bb2ef --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32210.238 +MinimumVisualStudioVersion = 15.0 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Google.Authenticator", "Google.Authenticator\Google.Authenticator.csproj", "{3D92DE47-0AB8-466F-9083-AF65D865E4BE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Google.Authenticator.WinTest", "Google.Authenticator.WinTest\Google.Authenticator.WinTest.csproj", "{C2B44C17-B77B-4DA8-B924-96B28B50D198}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Google.Authenticator.WebSample", "Google.Authenticator.WebSample\Google.Authenticator.WebSample.csproj", "{21A63F79-D85F-4FE7-AC74-5171FAC0DCBF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Google.Authenticator.Tests", "Google.Authenticator.Tests\Google.Authenticator.Tests.csproj", "{5671E1C5-7CD0-4F42-8FFD-33879A9663DA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3D92DE47-0AB8-466F-9083-AF65D865E4BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3D92DE47-0AB8-466F-9083-AF65D865E4BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3D92DE47-0AB8-466F-9083-AF65D865E4BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3D92DE47-0AB8-466F-9083-AF65D865E4BE}.Release|Any CPU.Build.0 = Release|Any CPU + {C2B44C17-B77B-4DA8-B924-96B28B50D198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C2B44C17-B77B-4DA8-B924-96B28B50D198}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C2B44C17-B77B-4DA8-B924-96B28B50D198}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C2B44C17-B77B-4DA8-B924-96B28B50D198}.Release|Any CPU.Build.0 = Release|Any CPU + {21A63F79-D85F-4FE7-AC74-5171FAC0DCBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21A63F79-D85F-4FE7-AC74-5171FAC0DCBF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21A63F79-D85F-4FE7-AC74-5171FAC0DCBF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21A63F79-D85F-4FE7-AC74-5171FAC0DCBF}.Release|Any CPU.Build.0 = Release|Any CPU + {5671E1C5-7CD0-4F42-8FFD-33879A9663DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5671E1C5-7CD0-4F42-8FFD-33879A9663DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5671E1C5-7CD0-4F42-8FFD-33879A9663DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5671E1C5-7CD0-4F42-8FFD-33879A9663DA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F3028003-C1B6-40B2-B69C-68F45D3DDE88} + EndGlobalSection +EndGlobal diff --git a/thirdparty/Google.Authenticator/Google.Authenticator/Base32Encoding.cs b/thirdparty/Google.Authenticator/Google.Authenticator/Base32Encoding.cs new file mode 100644 index 0000000000..0ea237fd72 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator/Base32Encoding.cs @@ -0,0 +1,140 @@ +using System; + +namespace Google.Authenticator +{ + /// + /// http://stackoverflow.com/questions/641361/base32-decoding + /// + public class Base32Encoding + { + /// + /// Base32 encoded string to byte[] + /// + /// Base32 encoded string + /// byte[] + public static byte[] ToBytes(string input) + { + if (string.IsNullOrEmpty(input)) + { + throw new ArgumentNullException(nameof(input)); + } + + input = input.TrimEnd('='); //remove padding characters + var byteCount = input.Length * 5 / 8; //this must be TRUNCATED + var returnArray = new byte[byteCount]; + + byte curByte = 0, bitsRemaining = 8; + int mask, arrayIndex = 0; + + foreach (var c in input) + { + var cValue = CharToValue(c); + + if (bitsRemaining > 5) + { + mask = cValue << (bitsRemaining - 5); + curByte = (byte) (curByte | mask); + bitsRemaining -= 5; + } + else + { + mask = cValue >> (5 - bitsRemaining); + curByte = (byte) (curByte | mask); + returnArray[arrayIndex++] = curByte; + curByte = (byte) (cValue << (3 + bitsRemaining)); + bitsRemaining += 3; + } + } + + //if we didn't end with a full byte + if (arrayIndex != byteCount) + returnArray[arrayIndex] = curByte; + + return returnArray; + } + + /// + /// byte[] to Base32 string, if starting from an ordinary string use Encoding.UTF8.GetBytes() to convert it to a byte[] + /// + /// byte[] of data to be Base32 encoded + /// Base32 String + public static string ToString(byte[] input) + { + if (input == null || input.Length == 0) + { + throw new ArgumentNullException(nameof(input)); + } + + var charCount = (int) Math.Ceiling(input.Length / 5d) * 8; + var returnArray = new char[charCount]; + + byte nextChar = 0, bitsRemaining = 5; + var arrayIndex = 0; + + foreach (var b in input) + { + nextChar = (byte) (nextChar | (b >> (8 - bitsRemaining))); + returnArray[arrayIndex++] = ValueToChar(nextChar); + + if (bitsRemaining < 4) + { + nextChar = (byte) ((b >> (3 - bitsRemaining)) & 31); + returnArray[arrayIndex++] = ValueToChar(nextChar); + bitsRemaining += 5; + } + + bitsRemaining -= 3; + nextChar = (byte) ((b << bitsRemaining) & 31); + } + + //if we didn't end with a full char + if (arrayIndex != charCount) + { + returnArray[arrayIndex++] = ValueToChar(nextChar); + while (arrayIndex != charCount) returnArray[arrayIndex++] = '='; //padding + } + + return new string(returnArray); + } + + private static int CharToValue(char c) + { + var value = (int) c; + + //65-90 == uppercase letters + if (value < 91 && value > 64) + { + return value - 65; + } + + //50-55 == numbers 2-7 + if (value < 56 && value > 49) + { + return value - 24; + } + + //97-122 == lowercase letters + if (value < 123 && value > 96) + { + return value - 97; + } + + throw new ArgumentException("Character is not a Base32 character.", nameof(c)); + } + + private static char ValueToChar(byte b) + { + if (b < 26) + { + return (char) (b + 65); + } + + if (b < 32) + { + return (char) (b + 24); + } + + throw new ArgumentException("Byte is not a value Base32 value.", nameof(b)); + } + } +} \ No newline at end of file diff --git a/thirdparty/Google.Authenticator/Google.Authenticator/Google.Authenticator.csproj b/thirdparty/Google.Authenticator/Google.Authenticator/Google.Authenticator.csproj new file mode 100644 index 0000000000..acdeefe35c --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator/Google.Authenticator.csproj @@ -0,0 +1,38 @@ + + + + net6.0 + Google Authenticator Two-Factor + Google Authenticator Two-Factor Authentication Library + Google Authenticator Two-Factor Authentication Library (Not officially affiliated with Google.) + Brandon Potter + Brandon Potter + 2.4.1 + Apache-2.0 + https://github.com/BrandonPotter/GoogleAuthenticator + GoogleAuthenticator + README.md + true + + + + + + + + + + + + + + NET6_0;NETCOREAPP + + + + + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + 2.0.1.1 + + diff --git a/thirdparty/Google.Authenticator/Google.Authenticator/MissingDependencyException.cs b/thirdparty/Google.Authenticator/Google.Authenticator/MissingDependencyException.cs new file mode 100644 index 0000000000..de4d02a270 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator/MissingDependencyException.cs @@ -0,0 +1,15 @@ +using System; + +namespace Google.Authenticator +{ + public class MissingDependencyException : Exception + { + public MissingDependencyException(string message) : base(message) + { + } + + public MissingDependencyException(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/thirdparty/Google.Authenticator/Google.Authenticator/QRException.cs b/thirdparty/Google.Authenticator/Google.Authenticator/QRException.cs new file mode 100644 index 0000000000..be89634534 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator/QRException.cs @@ -0,0 +1,13 @@ +using System; + +namespace Google.Authenticator +{ + public class QRException : Exception + { + public QRException(string message) : base(message) + { } + + public QRException(string message, Exception innerException) : base(message, innerException) + { } + } +} diff --git a/thirdparty/Google.Authenticator/Google.Authenticator/SetupCode.cs b/thirdparty/Google.Authenticator/Google.Authenticator/SetupCode.cs new file mode 100644 index 0000000000..7964d147b6 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator/SetupCode.cs @@ -0,0 +1,21 @@ +namespace Google.Authenticator +{ + public class SetupCode + { + public string Account { get; internal set; } + public string ManualEntryKey { get; internal set; } + /// + /// Base64-encoded PNG image + /// + public string QrCodeSetupImageUrl { get; internal set; } + + public SetupCode() { } + + public SetupCode(string account, string manualEntryKey, string qrCodeSetupImageUrl) + { + Account = account; + ManualEntryKey = manualEntryKey; + QrCodeSetupImageUrl = qrCodeSetupImageUrl; + } + } +} \ No newline at end of file diff --git a/thirdparty/Google.Authenticator/Google.Authenticator/TwoFactorAuthenticator.cs b/thirdparty/Google.Authenticator/Google.Authenticator/TwoFactorAuthenticator.cs new file mode 100644 index 0000000000..41ac921c71 --- /dev/null +++ b/thirdparty/Google.Authenticator/Google.Authenticator/TwoFactorAuthenticator.cs @@ -0,0 +1,321 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; + +using QRCoder; + +namespace Google.Authenticator +{ + /// + /// modified from + /// http://brandonpotter.com/2014/09/07/implementing-free-two-factor-authentication-in-net-using-google-authenticator/ + /// https://github.com/brandonpotter/GoogleAuthenticator + /// With elements borrowed from https://github.com/stephenlawuk/GoogleAuthenticator + /// + public class TwoFactorAuthenticator + { + private static readonly DateTime _epoch = + new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + private TimeSpan DefaultClockDriftTolerance { get; set; } + + public TwoFactorAuthenticator() => DefaultClockDriftTolerance = TimeSpan.FromMinutes(5); + + /// + /// Generate a setup code for a Google Authenticator user to scan + /// + /// Issuer ID (the name of the system, i.e. 'MyApp'), + /// can be omitted but not recommended https://github.com/google/google-authenticator/wiki/Key-Uri-Format + /// + /// Account Title (no spaces) + /// Account Secret Key + /// Flag saying if accountSecretKey is in Base32 format or original secret + /// Number of pixels per QR Module (2 pixels give ~ 100x100px QRCode, + /// should be 10 or less) + /// SetupCode object + public SetupCode GenerateSetupCode(string issuer, string accountTitleNoSpaces, string accountSecretKey, bool secretIsBase32, int qrPixelsPerModule = 3) => + GenerateSetupCode(issuer, accountTitleNoSpaces, ConvertSecretToBytes(accountSecretKey, secretIsBase32), qrPixelsPerModule); + + /// + /// Generate a setup code for a Google Authenticator user to scan + /// + /// Issuer ID (the name of the system, i.e. 'MyApp'), can be omitted but not + /// recommended https://github.com/google/google-authenticator/wiki/Key-Uri-Format + /// Account Title (no spaces) + /// Account Secret Key as byte[] + /// Number of pixels per QR Module + /// (2 = ~120x120px QRCode, should be 10 or less) + /// + /// SetupCode object + public SetupCode GenerateSetupCode(string issuer, + string accountTitleNoSpaces, + byte[] accountSecretKey, + int qrPixelsPerModule = 3, + bool generateQrCode = true) + { + if (string.IsNullOrWhiteSpace(accountTitleNoSpaces)) + { + throw new NullReferenceException("Account Title is null"); + } + + accountTitleNoSpaces = RemoveWhitespace(Uri.EscapeUriString(accountTitleNoSpaces)); + var encodedSecretKey = Base32Encoding.ToString(accountSecretKey); + + var provisionUrl = string.IsNullOrWhiteSpace(issuer) + ? $"otpauth://totp/{accountTitleNoSpaces}?secret={encodedSecretKey.Trim('=')}" + // https://github.com/google/google-authenticator/wiki/Conflicting-Accounts + // Added additional prefix to account otpauth://totp/Company:joe_example@gmail.com + // for backwards compatibility + : $"otpauth://totp/{UrlEncode(issuer)}:{accountTitleNoSpaces}?secret={encodedSecretKey.Trim('=')}&issuer={UrlEncode(issuer)}"; + + return new SetupCode( + accountTitleNoSpaces, + encodedSecretKey.Trim('='), + generateQrCode ? GenerateQrCodeUrl(qrPixelsPerModule, provisionUrl) : ""); + } + + private static string GenerateQrCodeUrl(int qrPixelsPerModule, string provisionUrl) + { + var qrCodeUrl = ""; + try + { + using (var qrGen = new QRCodeGenerator()) + using (var qrCode = qrGen.CreateQrCode(provisionUrl, QRCodeGenerator.ECCLevel.Q)) + using (var qrBmp = new BitmapByteQRCode(qrCode)) + { + qrCodeUrl = $"data:image/png;base64,{Convert.ToBase64String(qrBmp.GetGraphic(qrPixelsPerModule))}"; + } + } + catch (TypeInitializationException e) + { + if (e.InnerException != null + && e.InnerException.GetType() == typeof(DllNotFoundException) + && e.InnerException.Message.Contains("libgdiplus")) + { + throw new MissingDependencyException( + "It looks like libgdiplus has not been installed - see" + + " https://github.com/codebude/QRCoder/issues/227", + e); + } + else + { + throw; + } + } + catch (System.Runtime.InteropServices.ExternalException e) + { + if (e.Message.Contains("GDI+") && qrPixelsPerModule > 10) + { + throw new QRException( + $"There was a problem generating a QR code. The value of {nameof(qrPixelsPerModule)}" + + " should be set to a value of 10 or less for optimal results.", + e); + } + else + { + throw; + } + } + + return qrCodeUrl; + } + + private static string RemoveWhitespace(string str) => + new string(str.Where(c => !char.IsWhiteSpace(c)).ToArray()); + + private string UrlEncode(string value) + { + return Uri.EscapeDataString(value); + } + + /// + /// This method is generally called via /> + /// + /// The acount secret key as a string + /// The number of 30-second (by default) intervals since the unix epoch + /// The desired length of the returned PIN + /// Flag saying if accountSecretKey is in Base32 format or original secret + /// A 'PIN' that is valid for the specified time interval + public string GeneratePINAtInterval(string accountSecretKey, long counter, int digits = 6, bool secretIsBase32 = false) => + GeneratePINAtInterval(ConvertSecretToBytes(accountSecretKey, secretIsBase32), counter, digits); + + /// + /// This method is generally called via /> + /// + /// The acount secret key as a byte array + /// The number of 30-second (by default) intervals since the unix epoch + /// The desired length of the returned PIN + /// A 'PIN' that is valid for the specified time interval + public string GeneratePINAtInterval(byte[] accountSecretKey, long counter, int digits = 6) => + GenerateHashedCode(accountSecretKey, counter, digits); + + private string GenerateHashedCode(byte[] key, long iterationNumber, int digits = 6) + { + var counter = BitConverter.GetBytes(iterationNumber); + + if (BitConverter.IsLittleEndian) + Array.Reverse(counter); + + var hmac = new HMACSHA1(key); + var hash = hmac.ComputeHash(counter); + var offset = hash[hash.Length - 1] & 0xf; + + // Convert the 4 bytes into an integer, ignoring the sign. + var binary = + ((hash[offset] & 0x7f) << 24) + | (hash[offset + 1] << 16) + | (hash[offset + 2] << 8) + | hash[offset + 3]; + + var password = binary % (int)Math.Pow(10, digits); + return password.ToString(new string('0', digits)); + } + + private long GetCurrentCounter() => GetCurrentCounter(DateTime.UtcNow, _epoch, 30); + + private long GetCurrentCounter(DateTime now, DateTime epoch, int timeStep) => + (long)(now - epoch).TotalSeconds / timeStep; + + /// + /// Given a PIN from a client, check if it is valid at the current time. + /// + /// Account Secret Key + /// The PIN from the client + /// Flag saying if accountSecretKey is in Base32 format or original secret + /// True if PIN is currently valid + public bool ValidateTwoFactorPIN(string accountSecretKey, string twoFactorCodeFromClient, bool secretIsBase32 = false) => + ValidateTwoFactorPIN(accountSecretKey, twoFactorCodeFromClient, DefaultClockDriftTolerance, secretIsBase32); + + /// + /// Given a PIN from a client, check if it is valid at the current time. + /// + /// Account Secret Key + /// The PIN from the client + /// The time window within which to check to allow for clock drift between devices. + /// Flag saying if accountSecretKey is in Base32 format or original secret + /// True if PIN is currently valid + public bool ValidateTwoFactorPIN(string accountSecretKey, string twoFactorCodeFromClient, TimeSpan timeTolerance, bool secretIsBase32 = false) => + ValidateTwoFactorPIN(ConvertSecretToBytes(accountSecretKey, secretIsBase32), twoFactorCodeFromClient, timeTolerance); + + /// + /// Given a PIN from a client, check if it is valid at the current time. + /// + /// Account Secret Key + /// The PIN from the client + /// True if PIN is currently valid + public bool ValidateTwoFactorPIN(byte[] accountSecretKey, string twoFactorCodeFromClient) => + ValidateTwoFactorPIN(accountSecretKey, twoFactorCodeFromClient, DefaultClockDriftTolerance); + + /// + /// Given a PIN from a client, check if it is valid at the current time. + /// + /// Account Secret Key + /// The PIN from the client + /// The time window within which to check to allow for clock drift between devices. + /// True if PIN is currently valid + public bool ValidateTwoFactorPIN(byte[] accountSecretKey, string twoFactorCodeFromClient, TimeSpan timeTolerance) + { + return GetCurrentPINs(accountSecretKey, timeTolerance).Any(c => c == twoFactorCodeFromClient); + } + + /// + /// Get the PIN for current time; the same code that a 2FA app would generate for the current time. + /// Do not validate directly against this as clockdrift may cause a a different PIN to be generated than one you did a second ago. + /// + /// Account Secret Key + /// Flag saying if accountSecretKey is in Base32 format or original secret + /// A 6-digit PIN + public string GetCurrentPIN(string accountSecretKey, bool secretIsBase32 = false) => + GeneratePINAtInterval(accountSecretKey, GetCurrentCounter(), secretIsBase32: secretIsBase32); + + /// + /// Get the PIN for current time; the same code that a 2FA app would generate for the current time. + /// Do not validate directly against this as clockdrift may cause a a different PIN to be generated than one you did a second ago. + /// + /// Account Secret Key + /// The time you wish to generate the pin for + /// Flag saying if accountSecretKey is in Base32 format or original secret + /// A 6-digit PIN + public string GetCurrentPIN(string accountSecretKey, DateTime now, bool secretIsBase32 = false) => + GeneratePINAtInterval(accountSecretKey, GetCurrentCounter(now, _epoch, 30), secretIsBase32: secretIsBase32); + + /// + /// Get the PIN for current time; the same code that a 2FA app would generate for the current time. + /// Do not validate directly against this as clockdrift may cause a a different PIN to be generated. + /// + /// Account Secret Key + /// A 6-digit PIN + public string GetCurrentPIN(byte[] accountSecretKey) => + GeneratePINAtInterval(accountSecretKey, GetCurrentCounter()); + + /// + /// Get the PIN for current time; the same code that a 2FA app would generate for the current time. + /// Do not validate directly against this as clockdrift may cause a a different PIN to be generated. + /// + /// Account Secret Key + /// The time you wish to generate the pin for + /// A 6-digit PIN + public string GetCurrentPIN(byte[] accountSecretKey, DateTime now) => + GeneratePINAtInterval(accountSecretKey, GetCurrentCounter(now, _epoch, 30)); + + /// + /// Get all the PINs that would be valid within the time window allowed for by the default clock drift. + /// + /// Account Secret Key + /// Flag saying if accountSecretKey is in Base32 format or original secret + /// + public string[] GetCurrentPINs(string accountSecretKey, bool secretIsBase32 = false) => + GetCurrentPINs(accountSecretKey, DefaultClockDriftTolerance, secretIsBase32); + + /// + /// Get all the PINs that would be valid within the time window allowed for by the specified clock drift. + /// + /// Account Secret Key + /// The clock drift size you want to generate PINs for + /// Flag saying if accountSecretKey is in Base32 format or original secret + /// + public string[] GetCurrentPINs(string accountSecretKey, TimeSpan timeTolerance, bool secretIsBase32 = false) => + GetCurrentPINs(ConvertSecretToBytes(accountSecretKey, secretIsBase32), timeTolerance); + + /// + /// Get all the PINs that would be valid within the time window allowed for by the default clock drift. + /// + /// Account Secret Key + /// + public string[] GetCurrentPINs(byte[] accountSecretKey) => + GetCurrentPINs(accountSecretKey, DefaultClockDriftTolerance); + + /// + /// Get all the PINs that would be valid within the time window allowed for by the specified clock drift. + /// + /// Account Secret Key + /// The clock drift size you want to generate PINs for + /// + public string[] GetCurrentPINs(byte[] accountSecretKey, TimeSpan timeTolerance) + { + var codes = new List(); + var iterationCounter = GetCurrentCounter(); + var iterationOffset = 0; + + if (timeTolerance.TotalSeconds > 30) + { + iterationOffset = Convert.ToInt32(timeTolerance.TotalSeconds / 30.00); + } + + var iterationStart = iterationCounter - iterationOffset; + var iterationEnd = iterationCounter + iterationOffset; + + for (var counter = iterationStart; counter <= iterationEnd; counter++) + { + codes.Add(GeneratePINAtInterval(accountSecretKey, counter)); + } + + return codes.ToArray(); + } + + private static byte[] ConvertSecretToBytes(string secret, bool secretIsBase32) => + secretIsBase32 ? Base32Encoding.ToBytes(secret) : Encoding.UTF8.GetBytes(secret); + } +} \ No newline at end of file diff --git a/thirdparty/Google.Authenticator/LICENSE b/thirdparty/Google.Authenticator/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/thirdparty/Google.Authenticator/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/thirdparty/Google.Authenticator/README.md b/thirdparty/Google.Authenticator/README.md new file mode 100644 index 0000000000..edafd53a90 --- /dev/null +++ b/thirdparty/Google.Authenticator/README.md @@ -0,0 +1,43 @@ +# GoogleAuthenticator +Simple, easy to use server-side two-factor authentication library for .NET that works with Google Authenticator + +[![Build Status](https://dev.azure.com/brandon-potter/GoogleAuthenticator/_apis/build/status/BrandonPotter.GoogleAuthenticator?branchName=master)](https://dev.azure.com/brandon-potter/GoogleAuthenticator/_build/latest?definitionId=1&branchName=master) +[![NuGet Status](https://buildstats.info/nuget/GoogleAuthenticator)](https://www.nuget.org/packages/GoogleAuthenticator/) + +[`Install-Package GoogleAuthenticator`](https://www.nuget.org/packages/GoogleAuthenticator) + +## 1.x Usage +See blog post for usage instructions *(1.x only)*: + +https://csharprookie.wordpress.com/2015/03/17/implementing-free-two-factor-authentication-in-net-using-google-authenticator/ + +## 2.x Usage + +*Additional examples at [Google.Authenticator.WinTest](https://github.com/BrandonPotter/GoogleAuthenticator/tree/master/Google.Authenticator.WinTest) and [Google.Authenticator.WebSample](https://github.com/BrandonPotter/GoogleAuthenticator/tree/master/Google.Authenticator.WebSample)* + +```csharp +using Google.Authenticator; + +string key = Guid.NewGuid().ToString().Replace("-", "").Substring(0, 10); + +TwoFactorAuthenticator tfa = new TwoFactorAuthenticator(); +SetupCode setupInfo = tfa.GenerateSetupCode("Test Two Factor", "user@example.com", key, false, 3); + +string qrCodeImageUrl = setupInfo.QrCodeSetupImageUrl; +string manualEntrySetupCode = setupInfo.ManualEntryKey; + +imgQrCode.ImageUrl = qrCodeImageUrl; +lblManualSetupCode.Text = manualEntrySetupCode; + +// verify +TwoFactorAuthenticator tfa = new TwoFactorAuthenticator(); +bool result = tfa.ValidateTwoFactorPIN(key, txtCode.Text) +``` + +## Common Pitfalls + +* Old documentation indicated specifying width and height for the QR code, but changes in QR generation now uses pixels per module (QR "pixel") so using a value too high will result in a huge image that can overrun memory allocations +* Don't use the secret key and `ManualEntryKey` interchangeably. `ManualEntryKey` is used to enter into the authenticator app when scanning a QR code is impossible and is derived from the secret key ([discussion example](https://github.com/BrandonPotter/GoogleAuthenticator/issues/54)) + +# Notes +On linux, you need to ensure `libgdiplus` is installed if you want to generate QR Codes. See [https://github.com/codebude/QRCoder/issues/227](https://github.com/codebude/QRCoder/issues/227). diff --git a/web/ASC.Web.Core/ASC.Web.Core.csproj b/web/ASC.Web.Core/ASC.Web.Core.csproj index 33a7b4790e..5ec61e6b3d 100644 --- a/web/ASC.Web.Core/ASC.Web.Core.csproj +++ b/web/ASC.Web.Core/ASC.Web.Core.csproj @@ -211,7 +211,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive