├── .gitattributes ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── close-inavtive-issues.yml │ └── release.yml ├── .gitignore ├── APDU ├── APDU.csproj ├── AuthenticationRequest.cs ├── AuthenticationResponse.cs ├── Command.cs ├── CommandHeader.cs ├── CommandTrailer.cs ├── Constans.cs ├── DataReader.cs ├── DataWriter.cs ├── ErrorResponse.cs ├── IRawConvertible.cs ├── KnownFacets.cs ├── MessagePart.cs ├── RegisterRequest.cs ├── RegisterResponse.cs ├── Response.cs ├── Signature.cs ├── VersionRequest.cs └── VersionResposne.cs ├── JustTest ├── JustTest.csproj └── Program.cs ├── LICENSE ├── NativeBridge ├── Bridge.c ├── Bridge.h ├── NativeBridge.vcxproj └── NativeBridge.vcxproj.filters ├── Packing.ddf ├── README.md ├── SoftU2F.sln ├── SoftU2F.sln.DotSettings ├── SoftU2FDaemon ├── Program.cs ├── Properties │ └── PublishProfiles │ │ └── FolderProfile.pubxml ├── Resources │ ├── key.ico │ ├── lock.ico │ └── tray.ico └── SoftU2FDaemon.csproj ├── SoftU2FDriver ├── Device.c ├── Device.h ├── Driver.c ├── Driver.h ├── Public.h ├── ReadMe.txt ├── SoftU2F.vcxproj ├── SoftU2F.vcxproj.filters ├── SoftU2FDriver.inf ├── Trace.h ├── U2F.c └── U2F.h ├── SoftU2FDriverPackage ├── SoftU2FDriverPackage.vcxproj └── SoftU2FDriverPackage.vcxproj.filters └── U2FLib ├── Migrations ├── 20190619171922_InitialCreate.Designer.cs ├── 20190619171922_InitialCreate.cs └── AppDbContextModelSnapshot.cs ├── NativeBridge.dll ├── Storage ├── AppDBContext.cs └── KeyPair.cs ├── U2FBackgroundTask.cs ├── U2FHIDDeviceCommunication.cs ├── U2FHIDHandlers.cs ├── U2FLib.csproj ├── U2FRegistration.cs └── UserPresence.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | *.cs linguist-detectable=false 6 | 7 | ############################################################################### 8 | # Set default behavior for command prompt diff. 9 | # 10 | # This is need for earlier builds of msysgit that does not have it on by 11 | # default for csharp files. 12 | # Note: This is only used by command line 13 | ############################################################################### 14 | #*.cs diff=csharp 15 | 16 | ############################################################################### 17 | # Set the merge driver for project and solution files 18 | # 19 | # Merging from the command prompt will add diff markers to the files if there 20 | # are conflicts (Merging from VS is not affected by the settings below, in VS 21 | # the diff markers are never inserted). Diff markers may cause the following 22 | # file extensions to fail to load in VS. An alternative would be to treat 23 | # these files as binary and thus will always conflict and require user 24 | # intervention with every merge. To do so, just uncomment the entries below 25 | ############################################################################### 26 | #*.sln merge=binary 27 | #*.csproj merge=binary 28 | #*.vbproj merge=binary 29 | #*.vcxproj merge=binary 30 | #*.vcproj merge=binary 31 | #*.dbproj merge=binary 32 | #*.fsproj merge=binary 33 | #*.lsproj merge=binary 34 | #*.wixproj merge=binary 35 | #*.modelproj merge=binary 36 | #*.sqlproj merge=binary 37 | #*.wwaproj merge=binary 38 | 39 | ############################################################################### 40 | # behavior for image files 41 | # 42 | # image files are treated as binary by default. 43 | ############################################################################### 44 | #*.jpg binary 45 | #*.png binary 46 | #*.gif binary 47 | 48 | ############################################################################### 49 | # diff behavior for common document formats 50 | # 51 | # Convert binary document formats to text before diffing them. This feature 52 | # is only available from the command line. Turn it on by uncommenting the 53 | # entries below. 54 | ############################################################################### 55 | #*.doc diff=astextplain 56 | #*.DOC diff=astextplain 57 | #*.docx diff=astextplain 58 | #*.DOCX diff=astextplain 59 | #*.dot diff=astextplain 60 | #*.DOT diff=astextplain 61 | #*.pdf diff=astextplain 62 | #*.PDF diff=astextplain 63 | #*.rtf diff=astextplain 64 | #*.RTF diff=astextplain 65 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [ibigbug] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "nuget" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /.github/workflows/close-inavtive-issues.yml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | on: 3 | schedule: 4 | - cron: "30 1 * * *" 5 | 6 | jobs: 7 | close-issues: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/stale@v7 14 | with: 15 | days-before-issue-stale: 30 16 | days-before-issue-close: 14 17 | stale-issue-label: "stale" 18 | stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." 19 | close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." 20 | days-before-pr-stale: -1 21 | days-before-pr-close: -1 22 | repo-token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | # Sequence of patterns matched against refs/tags 4 | tags: 5 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 6 | 7 | name: Publish Daemon 8 | 9 | jobs: 10 | publish: 11 | name: Upload Release Asset 12 | runs-on: windows-latest 13 | steps: 14 | - name: Setup .NET Core SDK 15 | uses: actions/setup-dotnet@v3.0.2 16 | with: 17 | dotnet-version: '6.0.x' 18 | include-prerelease: true 19 | 20 | - name: Checkout code 21 | uses: actions/checkout@v3 22 | 23 | - name: Build project # This would actually build your project, using zip for an example artifact 24 | run: | 25 | dotnet publish --configuration Release --runtime win10-x64 --self-contained --output Release/win10-x64 --version-suffix ${{ github.run_number }} SoftU2FDaemon/SoftU2FDaemon.csproj 26 | Compress-Archive -Path Release/win10-x64\* -DestinationPath SoftU2FDaemon-SCD-win10-x64-${{ github.run_number }}.zip 27 | 28 | 29 | - name: Upload Release 30 | uses: softprops/action-gh-release@v1 31 | if: startsWith(github.ref, 'refs/tags/') 32 | with: 33 | files: | 34 | SoftU2FDaemon-SCD-win10-x64-${{ github.run_number }}.zip 35 | env: 36 | TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | 263 | disk1/ 264 | setup.inf 265 | setup.rpt -------------------------------------------------------------------------------- /APDU/APDU.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | None 9 | 10 | 11 | 12 | AnyCPU 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /APDU/AuthenticationRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace APDU 8 | { 9 | public partial class AuthenticationRequest: Command, IRawConvertible 10 | { 11 | public Control Control 12 | { 13 | get 14 | { 15 | if (Enum.IsDefined(typeof(Control), Header.p1)) 16 | { 17 | return (Control) Header.p1; 18 | } 19 | 20 | return Control.Invalid; 21 | } 22 | } 23 | 24 | public byte[] ChallengeParameter => Body.Skip(0).Take(Constants.U2F_CHAL_SIZE).ToArray(); 25 | 26 | public byte[] ApplicationParameter => 27 | Body.Skip(Constants.U2F_CHAL_SIZE).Take(Constants.U2F_APPID_SIZE).ToArray(); 28 | 29 | private int keyHandleLength => Body[Constants.U2F_CHAL_SIZE + Constants.U2F_APPID_SIZE]; 30 | 31 | public byte[] KeyHandle => Body.Skip(Constants.U2F_CHAL_SIZE + Constants.U2F_APPID_SIZE + 1) 32 | .Take(keyHandleLength).ToArray(); 33 | 34 | public AuthenticationRequest(byte[] challengeParameter, byte[] applicationParameter, byte[] keyHandle, 35 | Control control) 36 | { 37 | var stream = new MemoryStream(); 38 | using (var writer = new DataWriter(stream)) 39 | { 40 | writer.WriteBytes(challengeParameter); 41 | writer.WriteBytes(applicationParameter); 42 | writer.WriteByte((byte)keyHandle.Length); 43 | writer.WriteBytes(keyHandle); 44 | } 45 | 46 | Body = stream.ToArray(); 47 | Header = new CommandHeader(ins: CommandCode.Authenticate, p1: (byte) control, dataLength: Body.Length); 48 | Trailer = new CommandTrailer(noBody: true); 49 | 50 | } 51 | } 52 | 53 | partial class AuthenticationRequest 54 | { 55 | public AuthenticationRequest(byte[] raw) : base(raw) 56 | { 57 | } 58 | 59 | public override void ValidateBody() 60 | { 61 | if (Body.Length < Constants.U2F_CHAL_SIZE + Constants.U2F_APPID_SIZE + 1) 62 | throw ProtocolError.WithCode(ProtocolErrorCode.WrongLength); 63 | 64 | // to fit Conformance test.. 65 | if (keyHandleLength < 64) throw ProtocolError.WithCode(ProtocolErrorCode.WrongLength); 66 | 67 | if (Body.Length != Constants.U2F_CHAL_SIZE + Constants.U2F_APPID_SIZE + 1 + keyHandleLength) 68 | throw ProtocolError.WithCode(ProtocolErrorCode.WrongLength); 69 | 70 | if (Control == Control.Invalid) 71 | { 72 | throw ProtocolError.WithCode(ProtocolErrorCode.OtherError); 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /APDU/AuthenticationResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace APDU 8 | { 9 | public partial class AuthenticationResponse: Response, IRawConvertible 10 | { 11 | public byte UserPresence => Body[0]; 12 | 13 | public UInt32 Counter => BitConverter.ToUInt32(Body.Skip(1).Take(4).Reverse().ToArray(), 0); 14 | 15 | public byte[] Signature => Body.Skip(1 + 4).ToArray(); 16 | 17 | public AuthenticationResponse(byte userPresence, UInt32 counter, byte[] signature) 18 | { 19 | var stream = new MemoryStream(); 20 | using (var writer = new DataWriter(stream)) 21 | { 22 | writer.WriteByte(userPresence); 23 | writer.WriteUInt32(counter); 24 | writer.WriteBytes(signature); 25 | } 26 | 27 | Body = stream.ToArray(); 28 | Trailer = ProtocolErrorCode.NoError; 29 | } 30 | } 31 | 32 | partial class AuthenticationResponse 33 | { 34 | public AuthenticationResponse(byte[] data, ProtocolErrorCode trailer) 35 | { 36 | Init(data, trailer); 37 | } 38 | 39 | public override void Init(byte[] data, ProtocolErrorCode trailer) 40 | { 41 | Body = data; 42 | Trailer = trailer; 43 | } 44 | 45 | public override void ValidateBody() 46 | { 47 | // assuming 1 is the minimum signature size 48 | if (Body.Length < 1 + 4 + 1) throw ResponseError.WithError(ResponseErrorCode.BadSize); 49 | 50 | if (Trailer != ProtocolErrorCode.NoError) throw ResponseError.WithError(ResponseErrorCode.BadStatus); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /APDU/Command.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace APDU 9 | { 10 | 11 | public abstract partial class Command 12 | { 13 | public static CommandCode CommandType(byte[] data) 14 | { 15 | var reader = new DataReader(data); 16 | var header = new CommandHeader(reader); 17 | return header.ins; 18 | } 19 | 20 | public CommandHeader Header { get; set; } 21 | public byte[] Body { get; set; } 22 | public CommandTrailer Trailer { get; set; } 23 | 24 | public abstract void ValidateBody(); 25 | } 26 | 27 | partial class Command 28 | { 29 | public byte[] Raw 30 | { 31 | get 32 | { 33 | using (var stream = new MemoryStream()) 34 | using (var writer = new StreamWriter(stream)) 35 | { 36 | writer.Write(Header.Raw); 37 | writer.Write(Body); 38 | writer.Write(Trailer.Raw); 39 | return stream.ToArray(); 40 | } 41 | } 42 | } 43 | 44 | public void Init(byte[] raw) 45 | { 46 | var reader = new DataReader(raw); 47 | CommandHeader header; 48 | byte[] body; 49 | CommandTrailer trailer; 50 | 51 | try 52 | { 53 | header = new CommandHeader(reader); 54 | body = reader.ReadBytes(header.dataLength); 55 | trailer = new CommandTrailer(reader); 56 | } 57 | catch (DataReaderError e) 58 | { 59 | if (e.ErrorCode == DataReaderErrorCode.End) throw ProtocolError.WithCode(ProtocolErrorCode.WrongLength); 60 | throw; 61 | } 62 | 63 | Init(header, body, trailer); 64 | 65 | ValidateBody(); 66 | } 67 | 68 | protected Command(byte[] raw) 69 | { 70 | Init(raw); 71 | } 72 | 73 | protected Command() 74 | { 75 | } 76 | 77 | 78 | internal void Init(CommandHeader header, byte[] body, CommandTrailer trailer) 79 | { 80 | Header = header; 81 | Body = body; 82 | Trailer = trailer; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /APDU/CommandHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace APDU 10 | { 11 | public partial class CommandHeader : MessagePart, IRawConvertible 12 | { 13 | public CommandClass cla; 14 | public CommandCode ins; 15 | public byte p1; 16 | public byte p2; 17 | public int dataLength; 18 | 19 | public byte[] Raw 20 | { 21 | get 22 | { 23 | using (var output = new MemoryStream()) 24 | using (var writer = new DataWriter(output)) 25 | { 26 | writer.WriteByte((byte)cla); 27 | writer.WriteByte((byte)ins); 28 | writer.WriteByte(p1); 29 | writer.WriteByte(p2); 30 | 31 | if (dataLength > 0) 32 | { 33 | writer.WriteByte((byte)0x0); 34 | writer.WriteUint16(Convert.ToUInt16(dataLength)); 35 | } 36 | 37 | return output.ToArray(); 38 | } 39 | } 40 | } 41 | 42 | 43 | public CommandHeader(DataReader reader) 44 | { 45 | Init(reader); 46 | } 47 | 48 | public CommandHeader(CommandCode ins, int dataLength, byte p1 = 0x00, byte p2 = 0x00, CommandClass cla = CommandClass.Reserved) 49 | { 50 | this.cla = cla; 51 | this.ins = ins; 52 | this.p1 = p1; 53 | this.p2 = p2; 54 | this.dataLength = dataLength; 55 | } 56 | } 57 | 58 | partial class CommandHeader 59 | { 60 | public override void Init(DataReader reader) 61 | { 62 | try 63 | { 64 | var claByte = reader.ReadByte(); 65 | if (!Enum.IsDefined(typeof(CommandClass), claByte)) 66 | throw ProtocolError.WithCode(ProtocolErrorCode.ClassNotSupported); 67 | cla = (CommandClass)claByte; 68 | 69 | var insByte = reader.ReadByte(); 70 | if (!Enum.IsDefined(typeof(CommandCode), insByte)) 71 | throw ProtocolError.WithCode(ProtocolErrorCode.InsNotSupported); 72 | ins = (CommandCode)insByte; 73 | 74 | p1 = reader.ReadByte(); 75 | p2 = reader.ReadByte(); 76 | 77 | switch (reader.Remaining) 78 | { 79 | case 0: 80 | case 1: 81 | case 2: 82 | throw ProtocolError.WithCode(ProtocolErrorCode.WrongLength); 83 | case 3: 84 | dataLength = 0; 85 | break; 86 | 87 | default: 88 | var lc0 = reader.ReadByte(); 89 | if (lc0 != 0x00) throw ProtocolError.WithCode(ProtocolErrorCode.WrongLength); 90 | 91 | var lc = reader.ReadUInt16(); 92 | dataLength = lc; 93 | break; 94 | } 95 | } 96 | catch (EndOfStreamException) 97 | { 98 | throw ProtocolError.WithCode(ProtocolErrorCode.WrongLength); 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /APDU/CommandTrailer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace APDU 9 | { 10 | public class ProtocolError : Exception 11 | { 12 | public ProtocolErrorCode ErrorCode; 13 | 14 | private ProtocolError(ProtocolErrorCode code) 15 | { 16 | ErrorCode = code; 17 | } 18 | 19 | public static ProtocolError WithCode(ProtocolErrorCode code) 20 | { 21 | return new ProtocolError(code); 22 | } 23 | } 24 | 25 | public partial class CommandTrailer: MessagePart, IRawConvertible 26 | { 27 | private int MaxResponse; 28 | private bool NoBody; 29 | 30 | 31 | public CommandTrailer(DataReader reader) 32 | { 33 | Init(reader); 34 | } 35 | 36 | public CommandTrailer(bool noBody, int maxResponse = Constants.MaxResponseSize) 37 | { 38 | NoBody = noBody; 39 | MaxResponse = maxResponse; 40 | } 41 | } 42 | 43 | partial class CommandTrailer 44 | { 45 | public byte[] Raw 46 | { 47 | get 48 | { 49 | using (var stream = new MemoryStream()) 50 | using (var writer = new DataWriter(stream)) 51 | { 52 | if (NoBody) 53 | { 54 | writer.WriteByte((byte)(0x00)); 55 | } 56 | 57 | if (MaxResponse < UInt16.MaxValue) 58 | { 59 | writer.WriteUint16(Convert.ToUInt16(MaxResponse)); 60 | } 61 | else 62 | { 63 | writer.WriteUint16(Convert.ToUInt16(0x00)); 64 | } 65 | 66 | return stream.ToArray(); 67 | } 68 | } 69 | } 70 | 71 | public override void Init(DataReader reader) 72 | { 73 | if (reader.Remaining == 3) 74 | { 75 | NoBody = true; 76 | var zero = reader.ReadByte(); 77 | if (zero != 0x00) 78 | { 79 | throw ProtocolError.WithCode(ProtocolErrorCode.WrongLength); 80 | } 81 | } 82 | else 83 | { 84 | NoBody = false; 85 | } 86 | 87 | switch (reader.Remaining) 88 | { 89 | case 0: 90 | MaxResponse = 0; 91 | break; 92 | case 1: 93 | throw ProtocolError.WithCode(ProtocolErrorCode.WrongLength); 94 | case 2: 95 | var mr = reader.ReadUInt16(); 96 | MaxResponse = mr == 0x0000 ? Constants.MaxResponseSize : mr; 97 | 98 | break; 99 | default: 100 | throw ProtocolError.WithCode(ProtocolErrorCode.WrongLength); 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /APDU/Constans.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace APDU 8 | { 9 | public static class Constants 10 | { 11 | public const int MaxResponseSize = UInt16.MaxValue + 1; 12 | 13 | public const int U2F_CHAL_SIZE = 32; 14 | public const int U2F_APPID_SIZE = 32; 15 | 16 | public const int U2F_EC_KEY_SIZE = 32; // EC key size in bytes 17 | public const int U2F_EC_POINT_SIZE = ((U2F_EC_KEY_SIZE * 2) + 1); // Size of EC point 18 | } 19 | public enum CommandCode : byte 20 | { 21 | Register = 0x01, 22 | Authenticate, 23 | Version, 24 | CheckRegister, 25 | AuthenticateBatch, 26 | } 27 | 28 | public enum Control : byte 29 | { 30 | EnforceUserPresenceAndSign = 0x03, 31 | CheckOnly = 0x07, 32 | 33 | Invalid = 0xFF, 34 | } 35 | 36 | public enum CommandClass : byte 37 | { 38 | Reserved = 0x00, 39 | } 40 | 41 | public enum ResponseErrorCode : uint 42 | { 43 | BadSize, 44 | BadStatus, 45 | BadCertificate, 46 | BadData 47 | } 48 | 49 | public enum ProtocolErrorCode : UInt16 50 | { 51 | NoError = 0x9000, 52 | WrongData = 0x6A80, 53 | ConditionNoSatisfied = 0x6985, 54 | CommandNotAllowed = 0x6986, 55 | InsNotSupported = 0x6D00, 56 | WrongLength = 0x6700, 57 | ClassNotSupported = 0x6E00, 58 | OtherError = 0x6F00 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /APDU/DataReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace APDU 9 | { 10 | public enum DataReaderErrorCode:byte 11 | { 12 | End = 0 13 | } 14 | 15 | public class DataReaderError : Exception 16 | { 17 | public DataReaderErrorCode ErrorCode; 18 | 19 | private DataReaderError(DataReaderErrorCode code) 20 | { 21 | ErrorCode = code; 22 | } 23 | 24 | public static DataReaderError WithCode(DataReaderErrorCode code) 25 | { 26 | return new DataReaderError(code); 27 | } 28 | } 29 | public class DataReader 30 | { 31 | private readonly byte[] data; 32 | private int _offset; 33 | 34 | public int Remaining => data.Length - _offset; 35 | 36 | public DataReader(byte[] data, int offset = 0) 37 | { 38 | this.data = data; 39 | this._offset = offset; 40 | } 41 | 42 | public byte ReadByte() 43 | { 44 | var rv = data.Skip(_offset).Take(1).First(); 45 | _offset += 1; 46 | return rv; 47 | } 48 | 49 | public ushort ReadUInt16() 50 | { 51 | var bytes = data.Skip(_offset).Take(2).ToArray(); 52 | _offset += 2; 53 | if (BitConverter.IsLittleEndian) Array.Reverse(bytes); 54 | return BitConverter.ToUInt16(bytes, 0); 55 | } 56 | 57 | public uint ReadUInt32() 58 | { 59 | var bytes = data.Skip(_offset).Take(4).ToArray(); 60 | _offset += 4; 61 | if (BitConverter.IsLittleEndian) Array.Reverse(bytes); 62 | return BitConverter.ToUInt32(bytes, 0); 63 | } 64 | 65 | public byte[] ReadBytes(int n) 66 | { 67 | if (n > Remaining) throw DataReaderError.WithCode(DataReaderErrorCode.End); 68 | 69 | var rv = data.Skip(_offset).Take(n).ToArray(); 70 | _offset += n; 71 | return rv; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /APDU/DataWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace APDU 9 | { 10 | class DataWriter : IDisposable 11 | { 12 | private readonly Stream _stream; 13 | private readonly BinaryWriter _writer; 14 | 15 | public DataWriter(Stream stream) 16 | { 17 | this._stream = stream; 18 | _writer = new BinaryWriter(stream); 19 | } 20 | 21 | public void WriteByte(byte b) 22 | { 23 | _writer.Write(b); 24 | } 25 | 26 | public void WriteUint16(UInt16 b) 27 | { 28 | _writer.Write(BitConverter.GetBytes(b).Reverse().ToArray()); 29 | } 30 | 31 | public void WriteUInt32(UInt32 b) 32 | { 33 | _writer.Write(BitConverter.GetBytes(b).Reverse().ToArray()); 34 | } 35 | 36 | public void WriteBytes(byte[] bs) 37 | { 38 | _writer.Write(bs); 39 | } 40 | 41 | public void Dispose() 42 | { 43 | _writer?.Dispose(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /APDU/ErrorResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace APDU 8 | { 9 | public partial class ErrorResponse : Response, IRawConvertible 10 | { 11 | public ErrorResponse(ProtocolErrorCode trailer, byte[] body=default) 12 | { 13 | Init(body, trailer); 14 | } 15 | } 16 | 17 | public partial class ErrorResponse 18 | { 19 | public override void Init(byte[] data, ProtocolErrorCode trailer) 20 | { 21 | Body = default; 22 | Trailer = trailer; 23 | } 24 | 25 | public override void ValidateBody() 26 | { 27 | if (Body.Length != 0) 28 | { 29 | throw ResponseError.WithError(ResponseErrorCode.BadSize); 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /APDU/IRawConvertible.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace APDU 8 | { 9 | public interface IRawConvertible 10 | { 11 | byte[] Raw { get; } 12 | 13 | void Init(byte[] raw); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /APDU/KnownFacets.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security.Cryptography; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace APDU 9 | { 10 | public class KnownFacets 11 | { 12 | 13 | private static readonly Dictionary KnownFacetsMap = new Dictionary 14 | { 15 | { GetDigest("https://github.com/u2f/trusted_facets"), "https://github.com" }, 16 | { GetDigest("https://demo.yubico.com"), "https://demo.yubico.com" }, 17 | { GetDigest("https://www.dropbox.com/u2f-app-id.json"), "https://dropbox.com" }, 18 | { GetDigest("https://www.gstatic.com/securitykey/origins.json"), "https://google.com" }, 19 | { GetDigest("https://vault.bitwarden.com/app-id.json"), "https://vault.bitwarden.com" }, 20 | { GetDigest("https://keepersecurity.com"), "https://keepersecurity.com" }, 21 | { GetDigest("https://api-fdf6878a.duosecurity.com"), "https://api-fdf6878a.duosecurity.com" }, 22 | { GetDigest("https://dashboard.stripe.com"), "https://dashboard.stripe.com" }, 23 | { GetDigest("https://id.fedoraproject.org/u2f-origins.json"), "https://id.fedoraproject.org" }, 24 | { GetDigest("https://lastpass.com"), "https://lastpass.com" }, 25 | 26 | { Encoding.ASCII.GetBytes("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), "bogus" } 27 | }; 28 | 29 | public static string GetKnownFacet(byte[] key) 30 | { 31 | return KnownFacetsMap.TryGetValue(key, out var facet) ? facet : string.Empty; 32 | } 33 | 34 | 35 | private static byte[] GetDigest(string s) 36 | { 37 | using (var sha256Hasher = SHA256.Create()) 38 | { 39 | return sha256Hasher.ComputeHash(Encoding.UTF8.GetBytes(s)); 40 | } 41 | } 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /APDU/MessagePart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace APDU 9 | { 10 | public abstract class MessagePart 11 | { 12 | public abstract void Init(DataReader reader); 13 | 14 | public void Init(byte[] raw) 15 | { 16 | var reader = new DataReader(raw); 17 | Init(reader); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /APDU/RegisterRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace APDU 9 | { 10 | public partial class RegisterRequest : Command, IRawConvertible 11 | { 12 | public byte[] ChallengeParameter 13 | { 14 | get 15 | { 16 | var lowerBound = 0; 17 | return Body.Skip(lowerBound).Take(Constants.U2F_CHAL_SIZE).ToArray(); 18 | } 19 | } 20 | 21 | public byte[] ApplicationParameter 22 | { 23 | get 24 | { 25 | var lowerBound = Constants.U2F_CHAL_SIZE; 26 | return Body.Skip(lowerBound).Take(Constants.U2F_APPID_SIZE).ToArray(); 27 | } 28 | } 29 | 30 | public RegisterRequest(byte[] challengeParameter, byte[] applicationParameter) 31 | { 32 | var stream = new MemoryStream(); 33 | 34 | using (var writer = new DataWriter(stream)) 35 | { 36 | writer.WriteBytes(challengeParameter); 37 | writer.WriteBytes(applicationParameter); 38 | } 39 | 40 | Body = stream.ToArray(); 41 | Header = new CommandHeader(ins: CommandCode.Register, dataLength: Body.Length); 42 | Trailer = new CommandTrailer(noBody: false); 43 | } 44 | } 45 | 46 | partial class RegisterRequest 47 | { 48 | public RegisterRequest(byte[] raw) : base(raw) 49 | { 50 | } 51 | 52 | public override void ValidateBody() 53 | { 54 | if (Body.Length != Constants.U2F_CHAL_SIZE + Constants.U2F_APPID_SIZE) 55 | { 56 | throw ProtocolError.WithCode(ProtocolErrorCode.WrongLength); 57 | } 58 | } 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /APDU/RegisterResponse.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace APDU 6 | { 7 | public partial class RegisterResponse : Response, IRawConvertible 8 | { 9 | private short Reserved => Body.Skip(ReservedRange.Start).Take(ReservedRange.Length).First(); 10 | 11 | private int keyHandleLength => 12 | (int)Body.Skip(KeyHandleLengthRange.Start).Take(KeyHandleLengthRange.Length).First(); 13 | 14 | private (int Start, int Length) ReservedRange => (0, 1); 15 | 16 | private (int Start, int Length) PublicKeyRange => 17 | (ReservedRange.Start + ReservedRange.Length, Constants.U2F_EC_POINT_SIZE); 18 | 19 | private (int Start, int Length) KeyHandleLengthRange => (ReservedRange.Start + ReservedRange.Length, 1); 20 | 21 | private (int Start, int Length) KeyHandleRange => (KeyHandleLengthRange.Start + KeyHandleLengthRange.Length, 22 | keyHandleLength); 23 | 24 | private int certificateSize => Signature.GetCertificatePublicKeyInDer().Length; 25 | 26 | private (int Start, int Length) CertificateRange => 27 | (KeyHandleRange.Start + KeyHandleRange.Length, certificateSize); 28 | 29 | private (int Start, int Length) SignatureRange => (CertificateRange.Start + CertificateRange.Length, 30 | Body.Length - ReservedRange.Length - PublicKeyRange.Length - KeyHandleLengthRange.Length - 31 | KeyHandleRange.Length - CertificateRange.Length); 32 | 33 | public RegisterResponse(byte[] publicKey, byte[] keyHandle, byte[] certificate, byte[] signature) 34 | { 35 | var stream = new MemoryStream(); 36 | using (var writer = new DataWriter(stream)) 37 | { 38 | writer.WriteByte((byte)0x05); 39 | writer.WriteBytes(publicKey); 40 | writer.WriteByte((byte)keyHandle.Length); 41 | writer.WriteBytes(keyHandle); 42 | writer.WriteBytes(certificate); 43 | writer.WriteBytes(signature); 44 | } 45 | 46 | Body = stream.ToArray(); 47 | Trailer = ProtocolErrorCode.NoError; 48 | } 49 | } 50 | partial class RegisterResponse 51 | { 52 | public override void Init(byte[] body, ProtocolErrorCode trailer) 53 | { 54 | Body = body; 55 | Trailer = trailer; 56 | } 57 | 58 | public override void ValidateBody() 59 | { 60 | var min = Marshal.SizeOf() + Constants.U2F_EC_POINT_SIZE + Marshal.SizeOf(); 61 | if (Body.Length < min) throw ResponseError.WithError(ResponseErrorCode.BadSize); 62 | 63 | 64 | min += keyHandleLength + 1; 65 | if (Body.Length < min) throw ResponseError.WithError(ResponseErrorCode.BadSize); 66 | 67 | if (certificateSize == 0) throw ResponseError.WithError(ResponseErrorCode.BadCertificate); 68 | 69 | min += certificateSize + 1; 70 | if (Body.Length < min) throw ResponseError.WithError(ResponseErrorCode.BadSize); 71 | 72 | if (Reserved != 0x05) throw ResponseError.WithError(ResponseErrorCode.BadData); 73 | 74 | if (Trailer != ProtocolErrorCode.NoError) throw ResponseError.WithError(ResponseErrorCode.BadStatus); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /APDU/Response.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace APDU 9 | { 10 | public class ResponseError : Exception 11 | { 12 | public ResponseErrorCode ErrorCode; 13 | ResponseError(ResponseErrorCode code) 14 | { 15 | ErrorCode = code; 16 | } 17 | 18 | public static ResponseError WithError(ResponseErrorCode code) 19 | { 20 | return new ResponseError(code); 21 | } 22 | } 23 | 24 | public abstract partial class Response 25 | { 26 | public byte[] Body { get; internal set; } 27 | public ProtocolErrorCode Trailer { get; internal set; } 28 | 29 | public abstract void Init(byte[] data, ProtocolErrorCode trailer); 30 | 31 | public abstract void ValidateBody(); 32 | } 33 | 34 | // Implement IRawConvertible 35 | 36 | public partial class Response 37 | { 38 | public byte[] Raw 39 | { 40 | get 41 | { 42 | var stream = new MemoryStream(); 43 | using(var writer = new DataWriter(stream)) 44 | { 45 | if (Body != default) 46 | { 47 | writer.WriteBytes(Body); 48 | } 49 | writer.WriteUint16((ushort)Trailer); 50 | } 51 | return stream.ToArray(); 52 | } 53 | } 54 | 55 | public void Init(byte[] raw) 56 | { 57 | var reader = new DataReader(raw); 58 | var body = reader.ReadBytes(reader.Remaining - 2); 59 | var trailer = (ProtocolErrorCode) reader.ReadUInt16(); 60 | 61 | Init(body, trailer); 62 | 63 | ValidateBody(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /APDU/Signature.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using Org.BouncyCastle.Crypto; 6 | using Org.BouncyCastle.OpenSsl; 7 | using Org.BouncyCastle.Security; 8 | using Org.BouncyCastle.X509; 9 | 10 | namespace APDU 11 | { 12 | public static class Signature 13 | { 14 | private const string CertificatePem = 15 | "-----BEGIN CERTIFICATE-----\nMIIBfjCCASSgAwIBAgIBATAKBggqhkjOPQQDAjA8MREwDwYDVQQDDAhTb2Z0IFUyRjEUMBIGA1UECgwLR2l0SHViIEluYy4xETAPBgNVBAsMCFNlY3VyaXR5MB4XDTE3MDcyNjIwMDkwOFoXDTI3MDcyNDIwMDkwOFowPDERMA8GA1UEAwwIU29mdCBVMkYxFDASBgNVBAoMC0dpdEh1YiBJbmMuMREwDwYDVQQLDAhTZWN1cml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPacqyQUS7Tvh/cPIxxc1PV4BKz44Mays+NSGD2AOR9r0nnSakyDZHTmwtojk/+sHVA0bFwjkGVXkz7Lk/9u3tGjFzAVMBMGCysGAQQBguUcAgEBBAQDAgMIMAoGCCqGSM49BAMCA0gAMEUCIQD+Ih2XuOrqErufQhSFD0gXZbXglZNeoaPWbQ+xbzn3IgIgZNfcL1xsOCr3ZfV4ajmwsUqXRSjvfd8hAhUbiErUQXo=\n-----END CERTIFICATE-----"; 16 | private const string PrivateKeyPem = 17 | "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIAOEKsf0zeNn3qBWxk9/OxXqfUvEg8rGl58qMZOtVzEJoAoGCCqGSM49AwEHoUQDQgAE9pyrJBRLtO+H9w8jHFzU9XgErPjgxrKz41IYPYA5H2vSedJqTINkdObC2iOT/6wdUDRsXCOQZVeTPsuT/27e0Q==\n-----END EC PRIVATE KEY-----"; 18 | private const string SignerName = "SHA256/ECDSA"; 19 | 20 | private static X509Certificate LoadCertificate() 21 | { 22 | var reader = new StringReader(CertificatePem); 23 | var pem = new PemReader(reader); 24 | return (X509Certificate)pem.ReadObject(); 25 | } 26 | 27 | private static AsymmetricKeyParameter LoadPrivateKey() 28 | { 29 | var reader = new StringReader(PrivateKeyPem); 30 | var pem = new PemReader(reader); 31 | var keyPair = (AsymmetricCipherKeyPair) pem.ReadObject(); 32 | return keyPair.Private; 33 | } 34 | 35 | public static byte[] SignData(byte[] data) 36 | { 37 | return SignData(data, LoadPrivateKey()); 38 | } 39 | 40 | public static byte[] SignData(byte[] data, byte[] privateKey) 41 | { 42 | return SignData(data, PrivateKeyFactory.CreateKey(privateKey)); 43 | } 44 | 45 | public static byte[] SignData(byte[] data, AsymmetricKeyParameter privateKey) 46 | { 47 | var signer = SignerUtilities.GetSigner(SignerName); 48 | signer.Init(true, privateKey); 49 | signer.BlockUpdate(data, 0, data.Length); 50 | return signer.GenerateSignature(); 51 | } 52 | 53 | public static byte[] GetCertificatePublicKeyInDer() 54 | { 55 | var reader = new StringReader(CertificatePem); 56 | var pem = new PemReader(reader); 57 | return pem.ReadPemObject().Content; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /APDU/VersionRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace APDU 8 | { 9 | public class VersionRequest: Command, IRawConvertible 10 | { 11 | public VersionRequest(byte[] raw) : base(raw) 12 | { 13 | } 14 | 15 | public override void ValidateBody() 16 | { 17 | if (Body.Length > 0) 18 | { 19 | throw ProtocolError.WithCode(ProtocolErrorCode.WrongLength); 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /APDU/VersionResposne.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace APDU 5 | { 6 | public partial class VersionResponse : Response, IRawConvertible 7 | { 8 | public string Version => Encoding.UTF8.GetString(Body); 9 | 10 | public VersionResponse(string version) 11 | { 12 | Body = Encoding.UTF8.GetBytes(version); 13 | Trailer = ProtocolErrorCode.NoError; 14 | } 15 | } 16 | 17 | partial class VersionResponse 18 | { 19 | public override void Init(byte[] data, ProtocolErrorCode trailer) 20 | { 21 | Body = data; 22 | Trailer = trailer; 23 | } 24 | 25 | public override void ValidateBody() 26 | { 27 | if (Encoding.UTF8.GetBytes(Version).Length < 1) 28 | { 29 | throw ResponseError.WithError(ResponseErrorCode.BadSize); 30 | } 31 | 32 | if (Trailer != ProtocolErrorCode.NoError) 33 | { 34 | throw ResponseError.WithError(ResponseErrorCode.BadStatus); 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /JustTest/JustTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0-windows7.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /JustTest/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | 4 | namespace JustTest 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | var data = new byte[]{1,2,3}; 11 | var s = ProtectedData.Protect(data, null, DataProtectionScope.CurrentUser); 12 | Console.WriteLine("encrypted"); 13 | foreach (var b in s) 14 | { 15 | Console.Write(b); 16 | Console.Write(","); 17 | } 18 | 19 | var ss = ProtectedData.Unprotect(s, null, DataProtectionScope.CurrentUser); 20 | Console.WriteLine("Unencrypted"); 21 | foreach (var b in ss) 22 | { 23 | Console.Write(b); 24 | Console.Write(","); 25 | } 26 | 27 | Console.ReadLine(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /NativeBridge/Bridge.c: -------------------------------------------------------------------------------- 1 | #include "Bridge.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | EXTERN_C_START 9 | 10 | PWCHAR GetInterfaceDevicePath() 11 | { 12 | 13 | HDEVINFO hardwareDeviceInfo; 14 | SP_DEVICE_INTERFACE_DATA deviceInterfaceData; 15 | PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL; 16 | ULONG predictedLength; 17 | ULONG requiredLength = 0; 18 | ULONG i = 0; 19 | 20 | hardwareDeviceInfo = SetupDiGetClassDevs( 21 | (LPGUID)& GUID_DEVINTERFACE_SOFTU2F_FILTER, 22 | NULL, 23 | NULL, 24 | (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE) 25 | ); 26 | 27 | if(hardwareDeviceInfo == INVALID_HANDLE_VALUE) 28 | { 29 | printf("SetupDiGetClassDevs failed: %x\n", GetLastError()); 30 | return NULL; 31 | } 32 | 33 | deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); 34 | 35 | printf("\nList of Toaster Device Interfaces\n"); 36 | printf("---------------------------------\n"); 37 | 38 | do 39 | { 40 | if (SetupDiEnumDeviceInterfaces( 41 | hardwareDeviceInfo, 42 | 0, 43 | (LPGUID)&GUID_DEVINTERFACE_SOFTU2F_FILTER,i++,&deviceInterfaceData)) 44 | { 45 | if(deviceInterfaceDetailData) 46 | { 47 | free(deviceInterfaceDetailData); 48 | deviceInterfaceDetailData = NULL; 49 | } 50 | 51 | if (!SetupDiGetDeviceInterfaceDetail( 52 | hardwareDeviceInfo, 53 | &deviceInterfaceData, 54 | NULL, 55 | 0, 56 | &requiredLength,NULL)) 57 | { 58 | if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) 59 | { 60 | printf("SetupDiGetDeviceInterfaceDetail failed %d\n", GetLastError()); 61 | SetupDiDestroyDeviceInfoList(hardwareDeviceInfo); 62 | return NULL; 63 | } 64 | } 65 | 66 | predictedLength = requiredLength; 67 | 68 | deviceInterfaceDetailData = malloc(predictedLength); 69 | 70 | if (deviceInterfaceDetailData) 71 | { 72 | deviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); 73 | } else 74 | { 75 | printf("Couldn't allocate %d bytes for device interface details.\n", predictedLength); 76 | SetupDiDestroyDeviceInfoList(hardwareDeviceInfo); 77 | return NULL; 78 | } 79 | 80 | if (!SetupDiGetDeviceInterfaceDetail( 81 | hardwareDeviceInfo, 82 | &deviceInterfaceData, 83 | deviceInterfaceDetailData, 84 | predictedLength, 85 | &requiredLength, 86 | NULL 87 | )) 88 | { 89 | printf("Error in SetupDiGetDeviceInterfaceDetail\n"); 90 | SetupDiDestroyDeviceInfoList(hardwareDeviceInfo); 91 | free(deviceInterfaceDetailData); 92 | return NULL; 93 | } 94 | 95 | printf("%d) %s\n", i, 96 | deviceInterfaceDetailData->DevicePath); 97 | } 98 | else if (GetLastError() != ERROR_NO_MORE_ITEMS) 99 | { 100 | free(deviceInterfaceDetailData); 101 | deviceInterfaceDetailData = NULL; 102 | continue; 103 | } 104 | else break;; 105 | } while (TRUE); 106 | 107 | SetupDiDestroyDeviceInfoList(hardwareDeviceInfo); 108 | if (!deviceInterfaceDetailData) 109 | { 110 | printf("No device interfaces present\n"); 111 | return NULL; 112 | } 113 | 114 | return (PWCHAR)&deviceInterfaceDetailData->DevicePath[0]; 115 | } 116 | 117 | EXTERN_C_END -------------------------------------------------------------------------------- /NativeBridge/Bridge.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 4 | // Windows Header Files 5 | #include 6 | #include 7 | 8 | 9 | EXTERN_C_START 10 | 11 | // {DC3DE777-BC7E-4063-86C9-69422E319AF7} 12 | DEFINE_GUID(GUID_DEVINTERFACE_SOFTU2F_FILTER, 13 | 0xdc3de777, 0xbc7e, 0x4063, 0x86, 0xc9, 0x69, 0x42, 0x2e, 0x31, 0x9a, 0xf7); 14 | 15 | __declspec(dllexport) PWCHAR GetInterfaceDevicePath(); 16 | 17 | EXTERN_C_END -------------------------------------------------------------------------------- /NativeBridge/NativeBridge.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | {54A914C2-7CE1-4F25-9722-8B90D5641689} 24 | Win32Proj 25 | NativeBridge 26 | 10.0 27 | 28 | 29 | 30 | DynamicLibrary 31 | true 32 | v143 33 | Unicode 34 | 35 | 36 | DynamicLibrary 37 | false 38 | v143 39 | true 40 | Unicode 41 | 42 | 43 | DynamicLibrary 44 | true 45 | v143 46 | Unicode 47 | 48 | 49 | DynamicLibrary 50 | false 51 | v143 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | true 78 | 79 | 80 | false 81 | $(SolutionDir)$(Platform)\$(Configuration)\ 82 | 83 | 84 | false 85 | 86 | 87 | 88 | NotUsing 89 | Level3 90 | Disabled 91 | true 92 | WIN32;_DEBUG;NATIVEBRIDGE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 93 | true 94 | 95 | 96 | 97 | 98 | Windows 99 | true 100 | false 101 | Setupapi.lib;%(AdditionalDependencies) 102 | 103 | 104 | 105 | 106 | NotUsing 107 | Level3 108 | Disabled 109 | true 110 | _DEBUG;NATIVEBRIDGE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 111 | true 112 | 113 | 114 | 115 | 116 | 117 | Windows 118 | true 119 | false 120 | setupapi.lib;%(AdditionalDependencies) 121 | 122 | 123 | 124 | 125 | NotUsing 126 | Level3 127 | MaxSpeed 128 | true 129 | true 130 | true 131 | WIN32;NDEBUG;NATIVEBRIDGE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 132 | true 133 | 134 | 135 | CompileAsC 136 | 137 | 138 | Windows 139 | true 140 | true 141 | true 142 | false 143 | Setupapi.lib;%(AdditionalDependencies) 144 | 145 | 146 | 147 | 148 | NotUsing 149 | Level3 150 | MaxSpeed 151 | true 152 | true 153 | true 154 | NDEBUG;NATIVEBRIDGE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 155 | true 156 | 157 | 158 | 159 | 160 | 161 | Windows 162 | true 163 | true 164 | true 165 | false 166 | setupapi.lib;%(AdditionalDependencies) 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /NativeBridge/NativeBridge.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | 23 | 24 | Source Files 25 | 26 | 27 | -------------------------------------------------------------------------------- /Packing.ddf: -------------------------------------------------------------------------------- 1 | ; 2 | .OPTION EXPLICIT ; Generate errors 3 | .Set CabinetFileCountThreshold=0 4 | .Set FolderFileCountThreshold=0 5 | .Set FolderSizeThreshold=0 6 | .Set MaxCabinetSize=0 7 | .Set MaxDiskFileCount=0 8 | .Set MaxDiskSize=0 9 | .Set CompressionType=MSZIP 10 | .Set Cabinet=on 11 | .Set Compress=on 12 | .Set CabinetNameTemplate=SoftU2F-Win.cab 13 | .Set DestinationDir=SoftU2F 14 | x64\Release\SoftU2FDriver\SoftU2FDriver.inf 15 | x64\Release\SoftU2FDriver\softu2f.cat 16 | x64\Release\SoftU2FDriver\SoftU2FDriver.sys 17 | x64\Release\SoftU2FDriver.pdb -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [SoftU2F-Win](https://ibigbug.online/softu2f-for-windows) is a software U2F authenticator for Windows. It emulates a hardware U2F HID device and performs cryptographic operations using the DPAPI. This tool works with Google Chrome. Running on other browsers hasn't been tested. 2 | 3 | We take the security of this project seriously. Report any security vulnerabilities to open-source@watfaq.com 4 | 5 | [![Publish Daemon](https://github.com/SoftU2F/SoftU2F-Win/actions/workflows/release.yml/badge.svg)](https://github.com/SoftU2F/SoftU2F-Win/actions/workflows/release.yml) 6 | 7 | ## Installation 8 | 9 | > **Please use it for production environment on your own risk** 10 | 11 | ### Download 12 | 13 | 1. Download the latest driver and daemon release at [Driver Release](https://github.com/SoftU2F/SoftU2F-Win/releases) 14 | 15 | 1. Run the `driver-install.ps1` in elevated powershell to install the driver. (Run `Set-ExecutionPolicy RemoteSigned` if needed) 16 | 17 | 18 | ## Usage 19 | 20 | The app runs in the background. When a site loaded in a U2F-compatible browser attempts to register or authenticate with the software token, you'll see a notification asking you to accept or reject the request. You can experiment on [Yubico's U2F demo site](https://demo.yubico.com/u2f). 21 | 22 | ### Command Line Arguments 23 | - *--db-unprotected* - this will save the key pair into a separated sqlite db without the DPAPI protection, this can be useful if you want to back up the db for some reasons. when enabled, the data will be saved in to `db.unprotected.sqlite`. You can find the databases under `$HOME\AppData\Roaming\SoftU2FDaemon` 24 | 25 | ### Registration 26 | 27 | ![Registration](https://user-images.githubusercontent.com/543405/59797397-e9ab4e80-9322-11e9-9f36-555b608f926d.png) 28 | 29 | ### Authentication 30 | 31 | ![Authentication](https://user-images.githubusercontent.com/543405/59797166-6c7fd980-9322-11e9-952d-c3f353a09a65.png) 32 | 33 | ## Uninstalling 34 | 35 | ### Driver 36 | 37 | 1. Right Click the Windows logo on you status bar and open Device Manager 38 | 2. Under Human Interface Devices, find **SoftU2F Device**, right click and select **Uninstall Device** 39 | 40 | ### Daemon 41 | 42 | 1. Exit App 43 | 2. Delete the folder where you extracted them. 44 | 45 | ## Security considerations 46 | 47 | This is a port of https://github.com/github/SoftU2F. 48 | 49 | Instead of macOS Keychain, we store data using Windows DPAPI, which is designed by Microsoft Windows to store data data such as passwords, keys, and connection strings. 50 | 51 | For more infomation of DPAPI: https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.protecteddata?view=netframework-4.8#remarks 52 | 53 | A [note](https://github.com/github/SoftU2F#security-considerations) from Github Team 54 | 55 | ## Development 56 | 57 | ### Prerequisites 58 | 59 | ### Driver 60 | 61 | Install: 62 | 63 | * Microsoft Visual Studio 64 | * Windows SDK 65 | * Windows Driver Kit (WDK) 66 | 67 | Download and tutorials can be found at: https://docs.microsoft.com/en-us/windows-hardware/drivers/gettingstarted/writing-a-very-small-kmdf--driver 68 | 69 | And you should be able to compile the driver in Visual Studio. 70 | 71 | ### Daemon 72 | 73 | Daemon is just an NET Core project, no extra requirement other than developing a normal NET Core apps. 74 | 75 | ## Sponsorships 76 | 77 | - Razoreye LTD's fund for the driver signing 78 | 79 | ## Known app-IDs/facets 80 | 81 | Every website using U2F has an app-ID. For example, the app-ID of [Yubico's U2F demo page](https://demo.yubico.com/u2f) is `https://demo.yubico.com`. When the low-level U2F authenticator receives a request to register/authenticate a website, it doesn't receive the friendly app-ID string. Instead, it receives a SHA256 digest of the app-ID. To be able to show a helpful alert message when a website is trying to register/authenticate, a list of app-ID digests is maintained in this repository. You can find the list [here](https://github.com/ibigbug/SoftU2F-Win/blob/master/APDU/KnownFacets.cs). If your company's app-ID is missing from this list, open a pull request to add it. 82 | 83 | ## Licensing 84 | 85 | This project is [Unlicensed](https://github.com/ibigbug/SoftU2F-Win/blob/master/LICENSE) yet. 86 | 87 | ## Credits 88 | 89 | Lots of credits to the original work of [SoftU2F](https://github.com/github/SoftU2F) done by Github team. 90 | -------------------------------------------------------------------------------- /SoftU2F.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32526.322 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NativeBridge", "NativeBridge\NativeBridge.vcxproj", "{54A914C2-7CE1-4F25-9722-8B90D5641689}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "APDU", "APDU\APDU.csproj", "{011BE5F9-1916-4549-B307-779097C922AF}" 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SoftU2FDriver", "SoftU2FDriver\SoftU2F.vcxproj", "{6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "U2FLib", "U2FLib\U2FLib.csproj", "{A0BEBDF6-1D0D-4085-910F-60AE9111613E}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SoftU2FDaemon", "SoftU2FDaemon\SoftU2FDaemon.csproj", "{DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}" 15 | ProjectSection(ProjectDependencies) = postProject 16 | {54A914C2-7CE1-4F25-9722-8B90D5641689} = {54A914C2-7CE1-4F25-9722-8B90D5641689} 17 | EndProjectSection 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JustTest", "JustTest\JustTest.csproj", "{551530B9-2C88-430A-B669-06F481FBB658}" 20 | EndProject 21 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SoftU2FDriverPackage", "SoftU2FDriverPackage\SoftU2FDriverPackage.vcxproj", "{B13752F9-1180-4716-9B4B-2D801305F15E}" 22 | EndProject 23 | Global 24 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 25 | Debug|Any CPU = Debug|Any CPU 26 | Debug|ARM = Debug|ARM 27 | Debug|ARM64 = Debug|ARM64 28 | Debug|x64 = Debug|x64 29 | Debug|x86 = Debug|x86 30 | Release|Any CPU = Release|Any CPU 31 | Release|ARM = Release|ARM 32 | Release|ARM64 = Release|ARM64 33 | Release|x64 = Release|x64 34 | Release|x86 = Release|x86 35 | EndGlobalSection 36 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 37 | {54A914C2-7CE1-4F25-9722-8B90D5641689}.Debug|Any CPU.ActiveCfg = Debug|Win32 38 | {54A914C2-7CE1-4F25-9722-8B90D5641689}.Debug|Any CPU.Build.0 = Debug|Win32 39 | {54A914C2-7CE1-4F25-9722-8B90D5641689}.Debug|ARM.ActiveCfg = Debug|Win32 40 | {54A914C2-7CE1-4F25-9722-8B90D5641689}.Debug|ARM64.ActiveCfg = Debug|Win32 41 | {54A914C2-7CE1-4F25-9722-8B90D5641689}.Debug|x64.ActiveCfg = Release|Win32 42 | {54A914C2-7CE1-4F25-9722-8B90D5641689}.Debug|x64.Build.0 = Release|Win32 43 | {54A914C2-7CE1-4F25-9722-8B90D5641689}.Debug|x86.ActiveCfg = Debug|Win32 44 | {54A914C2-7CE1-4F25-9722-8B90D5641689}.Debug|x86.Build.0 = Debug|Win32 45 | {54A914C2-7CE1-4F25-9722-8B90D5641689}.Release|Any CPU.ActiveCfg = Release|Win32 46 | {54A914C2-7CE1-4F25-9722-8B90D5641689}.Release|Any CPU.Build.0 = Release|Win32 47 | {54A914C2-7CE1-4F25-9722-8B90D5641689}.Release|ARM.ActiveCfg = Release|Win32 48 | {54A914C2-7CE1-4F25-9722-8B90D5641689}.Release|ARM64.ActiveCfg = Release|Win32 49 | {54A914C2-7CE1-4F25-9722-8B90D5641689}.Release|x64.ActiveCfg = Release|Win32 50 | {54A914C2-7CE1-4F25-9722-8B90D5641689}.Release|x64.Build.0 = Release|Win32 51 | {54A914C2-7CE1-4F25-9722-8B90D5641689}.Release|x86.ActiveCfg = Release|Win32 52 | {54A914C2-7CE1-4F25-9722-8B90D5641689}.Release|x86.Build.0 = Release|Win32 53 | {011BE5F9-1916-4549-B307-779097C922AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {011BE5F9-1916-4549-B307-779097C922AF}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {011BE5F9-1916-4549-B307-779097C922AF}.Debug|ARM.ActiveCfg = Debug|Any CPU 56 | {011BE5F9-1916-4549-B307-779097C922AF}.Debug|ARM.Build.0 = Debug|Any CPU 57 | {011BE5F9-1916-4549-B307-779097C922AF}.Debug|ARM64.ActiveCfg = Debug|Any CPU 58 | {011BE5F9-1916-4549-B307-779097C922AF}.Debug|ARM64.Build.0 = Debug|Any CPU 59 | {011BE5F9-1916-4549-B307-779097C922AF}.Debug|x64.ActiveCfg = Debug|Any CPU 60 | {011BE5F9-1916-4549-B307-779097C922AF}.Debug|x64.Build.0 = Debug|Any CPU 61 | {011BE5F9-1916-4549-B307-779097C922AF}.Debug|x86.ActiveCfg = Debug|Any CPU 62 | {011BE5F9-1916-4549-B307-779097C922AF}.Debug|x86.Build.0 = Debug|Any CPU 63 | {011BE5F9-1916-4549-B307-779097C922AF}.Release|Any CPU.ActiveCfg = Release|Any CPU 64 | {011BE5F9-1916-4549-B307-779097C922AF}.Release|Any CPU.Build.0 = Release|Any CPU 65 | {011BE5F9-1916-4549-B307-779097C922AF}.Release|ARM.ActiveCfg = Release|Any CPU 66 | {011BE5F9-1916-4549-B307-779097C922AF}.Release|ARM.Build.0 = Release|Any CPU 67 | {011BE5F9-1916-4549-B307-779097C922AF}.Release|ARM64.ActiveCfg = Release|Any CPU 68 | {011BE5F9-1916-4549-B307-779097C922AF}.Release|ARM64.Build.0 = Release|Any CPU 69 | {011BE5F9-1916-4549-B307-779097C922AF}.Release|x64.ActiveCfg = Release|Any CPU 70 | {011BE5F9-1916-4549-B307-779097C922AF}.Release|x64.Build.0 = Release|Any CPU 71 | {011BE5F9-1916-4549-B307-779097C922AF}.Release|x86.ActiveCfg = Release|Any CPU 72 | {011BE5F9-1916-4549-B307-779097C922AF}.Release|x86.Build.0 = Release|Any CPU 73 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Debug|Any CPU.ActiveCfg = Debug|Win32 74 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Debug|Any CPU.Build.0 = Debug|Win32 75 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Debug|ARM.ActiveCfg = Debug|ARM 76 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Debug|ARM.Build.0 = Debug|ARM 77 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Debug|ARM.Deploy.0 = Debug|ARM 78 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Debug|ARM64.ActiveCfg = Debug|ARM64 79 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Debug|ARM64.Build.0 = Debug|ARM64 80 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Debug|ARM64.Deploy.0 = Debug|ARM64 81 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Debug|x64.ActiveCfg = Debug|x64 82 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Debug|x64.Build.0 = Debug|x64 83 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Debug|x64.Deploy.0 = Debug|x64 84 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Debug|x86.ActiveCfg = Debug|Win32 85 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Debug|x86.Build.0 = Debug|Win32 86 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Debug|x86.Deploy.0 = Debug|Win32 87 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Release|Any CPU.ActiveCfg = Release|Win32 88 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Release|Any CPU.Build.0 = Release|Win32 89 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Release|ARM.ActiveCfg = Release|ARM 90 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Release|ARM.Build.0 = Release|ARM 91 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Release|ARM.Deploy.0 = Release|ARM 92 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Release|ARM64.ActiveCfg = Release|ARM64 93 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Release|ARM64.Build.0 = Release|ARM64 94 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Release|ARM64.Deploy.0 = Release|ARM64 95 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Release|x64.ActiveCfg = Release|x64 96 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Release|x64.Build.0 = Release|x64 97 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Release|x64.Deploy.0 = Release|x64 98 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Release|x86.ActiveCfg = Release|Win32 99 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Release|x86.Build.0 = Release|Win32 100 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88}.Release|x86.Deploy.0 = Release|Win32 101 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 102 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Debug|Any CPU.Build.0 = Debug|Any CPU 103 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Debug|ARM.ActiveCfg = Debug|Any CPU 104 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Debug|ARM.Build.0 = Debug|Any CPU 105 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Debug|ARM64.ActiveCfg = Debug|Any CPU 106 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Debug|ARM64.Build.0 = Debug|Any CPU 107 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Debug|x64.ActiveCfg = Debug|Any CPU 108 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Debug|x64.Build.0 = Debug|Any CPU 109 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Debug|x86.ActiveCfg = Debug|Any CPU 110 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Debug|x86.Build.0 = Debug|Any CPU 111 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Release|Any CPU.ActiveCfg = Release|Any CPU 112 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Release|Any CPU.Build.0 = Release|Any CPU 113 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Release|ARM.ActiveCfg = Release|Any CPU 114 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Release|ARM.Build.0 = Release|Any CPU 115 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Release|ARM64.ActiveCfg = Release|Any CPU 116 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Release|ARM64.Build.0 = Release|Any CPU 117 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Release|x64.ActiveCfg = Release|Any CPU 118 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Release|x64.Build.0 = Release|Any CPU 119 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Release|x86.ActiveCfg = Release|Any CPU 120 | {A0BEBDF6-1D0D-4085-910F-60AE9111613E}.Release|x86.Build.0 = Release|Any CPU 121 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 122 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Debug|Any CPU.Build.0 = Debug|Any CPU 123 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Debug|ARM.ActiveCfg = Debug|Any CPU 124 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Debug|ARM.Build.0 = Debug|Any CPU 125 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Debug|ARM64.ActiveCfg = Debug|Any CPU 126 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Debug|ARM64.Build.0 = Debug|Any CPU 127 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Debug|x64.ActiveCfg = Debug|Any CPU 128 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Debug|x64.Build.0 = Debug|Any CPU 129 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Debug|x86.ActiveCfg = Debug|Any CPU 130 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Debug|x86.Build.0 = Debug|Any CPU 131 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Release|Any CPU.ActiveCfg = Release|Any CPU 132 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Release|Any CPU.Build.0 = Release|Any CPU 133 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Release|ARM.ActiveCfg = Release|Any CPU 134 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Release|ARM.Build.0 = Release|Any CPU 135 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Release|ARM64.ActiveCfg = Release|Any CPU 136 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Release|ARM64.Build.0 = Release|Any CPU 137 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Release|x64.ActiveCfg = Release|Any CPU 138 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Release|x64.Build.0 = Release|Any CPU 139 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Release|x86.ActiveCfg = Release|Any CPU 140 | {DA6EEE3B-38E8-414B-BDBF-AD3B80BC5950}.Release|x86.Build.0 = Release|Any CPU 141 | {551530B9-2C88-430A-B669-06F481FBB658}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 142 | {551530B9-2C88-430A-B669-06F481FBB658}.Debug|Any CPU.Build.0 = Debug|Any CPU 143 | {551530B9-2C88-430A-B669-06F481FBB658}.Debug|ARM.ActiveCfg = Debug|Any CPU 144 | {551530B9-2C88-430A-B669-06F481FBB658}.Debug|ARM.Build.0 = Debug|Any CPU 145 | {551530B9-2C88-430A-B669-06F481FBB658}.Debug|ARM64.ActiveCfg = Debug|Any CPU 146 | {551530B9-2C88-430A-B669-06F481FBB658}.Debug|ARM64.Build.0 = Debug|Any CPU 147 | {551530B9-2C88-430A-B669-06F481FBB658}.Debug|x64.ActiveCfg = Debug|Any CPU 148 | {551530B9-2C88-430A-B669-06F481FBB658}.Debug|x64.Build.0 = Debug|Any CPU 149 | {551530B9-2C88-430A-B669-06F481FBB658}.Debug|x86.ActiveCfg = Debug|Any CPU 150 | {551530B9-2C88-430A-B669-06F481FBB658}.Debug|x86.Build.0 = Debug|Any CPU 151 | {551530B9-2C88-430A-B669-06F481FBB658}.Release|Any CPU.ActiveCfg = Release|Any CPU 152 | {551530B9-2C88-430A-B669-06F481FBB658}.Release|Any CPU.Build.0 = Release|Any CPU 153 | {551530B9-2C88-430A-B669-06F481FBB658}.Release|ARM.ActiveCfg = Release|Any CPU 154 | {551530B9-2C88-430A-B669-06F481FBB658}.Release|ARM.Build.0 = Release|Any CPU 155 | {551530B9-2C88-430A-B669-06F481FBB658}.Release|ARM64.ActiveCfg = Release|Any CPU 156 | {551530B9-2C88-430A-B669-06F481FBB658}.Release|ARM64.Build.0 = Release|Any CPU 157 | {551530B9-2C88-430A-B669-06F481FBB658}.Release|x64.ActiveCfg = Release|Any CPU 158 | {551530B9-2C88-430A-B669-06F481FBB658}.Release|x64.Build.0 = Release|Any CPU 159 | {551530B9-2C88-430A-B669-06F481FBB658}.Release|x86.ActiveCfg = Release|Any CPU 160 | {551530B9-2C88-430A-B669-06F481FBB658}.Release|x86.Build.0 = Release|Any CPU 161 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Debug|Any CPU.ActiveCfg = Debug|x64 162 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Debug|Any CPU.Build.0 = Debug|x64 163 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Debug|Any CPU.Deploy.0 = Debug|x64 164 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Debug|ARM.ActiveCfg = Debug|x64 165 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Debug|ARM.Build.0 = Debug|x64 166 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Debug|ARM.Deploy.0 = Debug|x64 167 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Debug|ARM64.ActiveCfg = Debug|ARM64 168 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Debug|ARM64.Build.0 = Debug|ARM64 169 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Debug|ARM64.Deploy.0 = Debug|ARM64 170 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Debug|x64.ActiveCfg = Debug|x64 171 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Debug|x64.Build.0 = Debug|x64 172 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Debug|x64.Deploy.0 = Debug|x64 173 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Debug|x86.ActiveCfg = Debug|x64 174 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Debug|x86.Build.0 = Debug|x64 175 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Debug|x86.Deploy.0 = Debug|x64 176 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Release|Any CPU.ActiveCfg = Release|x64 177 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Release|Any CPU.Build.0 = Release|x64 178 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Release|Any CPU.Deploy.0 = Release|x64 179 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Release|ARM.ActiveCfg = Release|x64 180 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Release|ARM.Build.0 = Release|x64 181 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Release|ARM.Deploy.0 = Release|x64 182 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Release|ARM64.ActiveCfg = Release|ARM64 183 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Release|ARM64.Build.0 = Release|ARM64 184 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Release|ARM64.Deploy.0 = Release|ARM64 185 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Release|x64.ActiveCfg = Release|x64 186 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Release|x64.Build.0 = Release|x64 187 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Release|x64.Deploy.0 = Release|x64 188 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Release|x86.ActiveCfg = Release|x64 189 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Release|x86.Build.0 = Release|x64 190 | {B13752F9-1180-4716-9B4B-2D801305F15E}.Release|x86.Deploy.0 = Release|x64 191 | EndGlobalSection 192 | GlobalSection(SolutionProperties) = preSolution 193 | HideSolutionNode = FALSE 194 | EndGlobalSection 195 | GlobalSection(ExtensibilityGlobals) = postSolution 196 | SolutionGuid = {8F4A9B3D-6DD1-463C-9795-F75CF97CFB44} 197 | EndGlobalSection 198 | EndGlobal 199 | -------------------------------------------------------------------------------- /SoftU2F.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True -------------------------------------------------------------------------------- /SoftU2FDaemon/Program.cs: -------------------------------------------------------------------------------- 1 | using ContextMenu = System.Windows.Forms.ContextMenuStrip; 2 | using MenuItem = System.Windows.Forms.ToolStripMenuItem; 3 | 4 | namespace SoftU2FDaemon 5 | { 6 | using Microsoft.EntityFrameworkCore; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Win32; 9 | using System; 10 | using System.Drawing; 11 | using System.IO; 12 | using System.Linq; 13 | using System.Runtime.InteropServices; 14 | using System.Threading; 15 | using System.Windows.Forms; 16 | using U2FLib; 17 | using U2FLib.Storage; 18 | 19 | internal class App : Form, INotifySender 20 | { 21 | private CancellationTokenSource _cancellation; 22 | 23 | private IServiceProvider _serviceProvider; 24 | private NotifyIcon _trayIcon; 25 | private ContextMenu _trayMenu; 26 | 27 | public App() 28 | { 29 | SetupApplication(); 30 | InitializeTrayIcon(); 31 | InitializeBackgroundDaemon(); 32 | 33 | if (DiagnoseMode) 34 | { 35 | tryOutNotification(); 36 | } 37 | } 38 | 39 | [STAThread] 40 | public static void Main() 41 | { 42 | Application.EnableVisualStyles(); 43 | Application.SetCompatibleTextRenderingDefault(false); 44 | Application.Run(new App()); 45 | } 46 | 47 | #region App settings 48 | 49 | private static readonly string BinName = typeof(App).Assembly.GetName().Name; 50 | 51 | private static readonly string BinFolder = Path.Combine( 52 | Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), BinName); 53 | 54 | private static readonly string DBPath = Path.Combine( 55 | BinFolder, "db.sqlite"); 56 | private static readonly string UnProtectedDBPath = Path.Combine(BinFolder, "db.unprotected.sqlite"); 57 | 58 | public static bool UnprotectedMode => Environment.GetCommandLineArgs().Contains("--db-unprotected"); 59 | 60 | public static bool DiagnoseMode => Environment.GetCommandLineArgs().Contains("--diagnose-mode"); 61 | 62 | #endregion 63 | 64 | #region Application LifeCycle 65 | 66 | private IntPtr lastActiveWin = IntPtr.Zero; 67 | 68 | [DllImport("user32.dll", ExactSpelling = true)] 69 | private static extern IntPtr GetForegroundWindow(); 70 | 71 | [DllImport("user32.dll")] 72 | [return: MarshalAs(UnmanagedType.Bool)] 73 | private static extern bool SetForegroundWindow(IntPtr hWnd); 74 | 75 | #endregion 76 | 77 | #region Application Initialization 78 | 79 | private void Restart() 80 | { 81 | Application.Restart(); 82 | } 83 | 84 | private void SetupApplication() 85 | { 86 | var serviceCollection = new ServiceCollection(); 87 | ConfigureServices(serviceCollection); 88 | _serviceProvider = serviceCollection.BuildServiceProvider(); 89 | 90 | var dbContext = _serviceProvider.GetService(); 91 | using (dbContext) 92 | { 93 | if (!Directory.Exists(BinFolder)) Directory.CreateDirectory(BinFolder); 94 | dbContext.Database.Migrate(); 95 | var appData = dbContext.ApplicationDatum.FirstOrDefault(); 96 | if (appData == null) 97 | { 98 | appData = new ApplicationData(); 99 | appData.Counter = 0; 100 | dbContext.ApplicationDatum.Add(appData); 101 | dbContext.SaveChanges(); 102 | } 103 | } 104 | 105 | _cancellation = new CancellationTokenSource(); 106 | 107 | _cancellation.Token.Register(() => { 108 | _cancellation.Dispose(); 109 | if (_exitRequested) { Environment.Exit(0); } 110 | }); 111 | } 112 | 113 | 114 | private void InitializeBackgroundDaemon() 115 | { 116 | var daemon = _serviceProvider.GetService(); 117 | if (!daemon.OpenDevice()) 118 | { 119 | MessageBox.Show("Failed to load driver. Maybe installation was unsuccessful\nExiting", "Driver Error"); 120 | if (Application.MessageLoop) Application.Exit(); 121 | Environment.Exit(1); 122 | return; 123 | } 124 | 125 | new Thread(() => { daemon.StartIoLoop(_cancellation.Token); }).Start(); 126 | UserPresence.Sender = this; 127 | } 128 | 129 | private void ConfigureServices(IServiceCollection service) 130 | { 131 | service.AddLogging(); 132 | service.AddSingleton(); 133 | 134 | if (UnprotectedMode) 135 | { 136 | service.AddDbContext(options => { options.UseSqlite($"Filename={UnProtectedDBPath}"); }); 137 | Environment.SetEnvironmentVariable("DBPath", UnProtectedDBPath); // for DbContext outside container 138 | } 139 | else 140 | { 141 | service.AddDbContext(options => { options.UseSqlite($"Filename={DBPath}"); }); 142 | Environment.SetEnvironmentVariable("DBPath", DBPath); // for DbContext outside container 143 | } 144 | } 145 | 146 | #endregion 147 | 148 | #region System Tray Icon 149 | 150 | private bool _exitRequested = false; 151 | 152 | private void InitializeTrayIcon() 153 | { 154 | _trayMenu = new ContextMenu(); 155 | 156 | var item = new MenuItem { Text = @"Auto Start", Checked = AutoStart() }; 157 | item.Click += OnAutoStartClick; 158 | _trayMenu.Items.Add(item); 159 | 160 | _trayMenu.Items.Add("Reset", null, OnResetClickedOnClick); 161 | _trayMenu.Items.Add("-"); 162 | _trayMenu.Items.Add("Exit", null, (sender, args) => { 163 | _exitRequested = true; 164 | Application.Exit(); 165 | }); 166 | 167 | _trayIcon = new NotifyIcon 168 | { 169 | Text = @"SoftU2F Daemon", 170 | ContextMenuStrip = _trayMenu, 171 | Icon = new Icon("tray.ico"), 172 | Visible = true 173 | }; 174 | 175 | _trayIcon.BalloonTipClicked += (sender, args) => 176 | { 177 | if (lastActiveWin != IntPtr.Zero) 178 | SetForegroundWindow(lastActiveWin); 179 | 180 | _notificationOpen = false; 181 | _userPresenceCallback?.Invoke(true); 182 | }; 183 | _trayIcon.BalloonTipShown += (sender, args) => 184 | { 185 | _notificationOpen = true; 186 | lastActiveWin = GetForegroundWindow(); 187 | }; 188 | _trayIcon.BalloonTipClosed += (sender, args) => _notificationOpen = false; 189 | 190 | this.FormClosing += (sender, e) => 191 | { 192 | // Hide and dispose the icon 193 | _trayIcon.Visible = false; 194 | _trayIcon.Dispose(); 195 | }; 196 | } 197 | 198 | private void OnAutoStartClick(object sender, EventArgs e) 199 | { 200 | if (AutoStart()) 201 | { 202 | using var key = 203 | Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true); 204 | key?.DeleteValue(BinName, false); 205 | } 206 | else 207 | { 208 | using var key = 209 | Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true); 210 | key?.SetValue(BinName, "\"" + Application.ExecutablePath + "\""); 211 | } 212 | 213 | var item = (MenuItem)sender; 214 | item.Checked = !item.Checked; 215 | } 216 | 217 | private static bool AutoStart() 218 | { 219 | using var key = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true); 220 | return key != null && key.GetValueNames().Any(v => v == BinName); 221 | } 222 | 223 | private void OnResetClickedOnClick(object sender, EventArgs args) 224 | { 225 | var confirm = MessageBox.Show(@"Do you want to reset SoftU2F? this will delete all your local data.", 226 | @"Reset Database", MessageBoxButtons.YesNo); 227 | if (confirm != DialogResult.Yes) 228 | { 229 | MessageBox.Show("Reset cancelled"); 230 | return; 231 | } 232 | 233 | if (!File.Exists(DBPath)) return; 234 | var bak = $"{DBPath}.bak"; 235 | if (File.Exists(bak)) File.Delete(bak); 236 | File.Move(DBPath, bak); 237 | Restart(); 238 | } 239 | 240 | protected override void OnLoad(EventArgs e) 241 | { 242 | Visible = false; 243 | ShowInTaskbar = false; 244 | base.OnLoad(e); 245 | } 246 | 247 | #endregion 248 | 249 | #region IDisposable 250 | 251 | public new void Dispose() 252 | { 253 | Dispose(true); 254 | GC.SuppressFinalize(this); 255 | } 256 | 257 | private readonly bool disposed = false; 258 | 259 | protected override void Dispose(bool disposing) 260 | { 261 | if (disposed) return; 262 | if (disposing) _cancellation.Cancel(); 263 | base.Dispose(disposing); 264 | } 265 | 266 | ~App() 267 | { 268 | Dispose(true); 269 | } 270 | 271 | #endregion 272 | 273 | #region UserPresence 274 | 275 | private Action _userPresenceCallback; 276 | private readonly object _userPresenceCallbackLock = new(); 277 | private bool _notificationOpen; 278 | 279 | private Action UserPresenceCallback 280 | { 281 | set 282 | { 283 | lock (_userPresenceCallbackLock) 284 | { 285 | _userPresenceCallback?.Invoke(false); 286 | _userPresenceCallback = value; 287 | } 288 | } 289 | } 290 | 291 | public void Send(string title, string message, Action userClicked) 292 | { 293 | if (_notificationOpen) return; 294 | _trayIcon.ShowBalloonTip((int)TimeSpan.FromSeconds(10).TotalMilliseconds, title, message, 295 | ToolTipIcon.Info); 296 | UserPresenceCallback = userClicked; 297 | } 298 | 299 | private void tryOutNotification() 300 | { 301 | _trayIcon.ShowBalloonTip((int)TimeSpan.FromSeconds(5).TotalMilliseconds, "Test Notification", "If you didn't see this, you'd probabaly have issue with handling authentication actions", ToolTipIcon.Info); 302 | } 303 | 304 | #endregion 305 | } 306 | } -------------------------------------------------------------------------------- /SoftU2FDaemon/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | FileSystem 8 | Release 9 | Any CPU 10 | net6.0-windows 11 | bin\Release\net6.0\publish\ 12 | true 13 | <_IsPortable>true 14 | win-x64 15 | true 16 | 17 | -------------------------------------------------------------------------------- /SoftU2FDaemon/Resources/key.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/SoftU2F-Win/a00de790ec4536f2b8bf3f2ba421751df0c6bbe8/SoftU2FDaemon/Resources/key.ico -------------------------------------------------------------------------------- /SoftU2FDaemon/Resources/lock.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/SoftU2F-Win/a00de790ec4536f2b8bf3f2ba421751df0c6bbe8/SoftU2FDaemon/Resources/lock.ico -------------------------------------------------------------------------------- /SoftU2FDaemon/Resources/tray.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/SoftU2F-Win/a00de790ec4536f2b8bf3f2ba421751df0c6bbe8/SoftU2FDaemon/Resources/tray.ico -------------------------------------------------------------------------------- /SoftU2FDaemon/SoftU2FDaemon.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net6.0-windows10.0.17763.0 6 | true 7 | Resources\tray.ico 8 | SoftU2FDaemon.App 9 | ibigbug 10 | Watfaq 11 | SoftU2F 12 | Software U2F authenticator for Windows 10 13 | https://github.com/ibigbug/softu2f-win 14 | https://github.com/ibigbug/softu2f-win 15 | u2f,security 16 | True 17 | 18 | 19 | 20 | None 21 | false 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | True 41 | True 42 | Resources.resx 43 | 44 | 45 | True 46 | True 47 | Settings.settings 48 | 49 | 50 | 51 | 52 | 53 | ResXFileCodeGenerator 54 | Resources.Designer.cs 55 | 56 | 57 | 58 | 59 | 60 | SettingsSingleFileGenerator 61 | Settings.Designer.cs 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /SoftU2FDriver/Device.c: -------------------------------------------------------------------------------- 1 | /*++ 2 | 3 | Module Name: 4 | 5 | device.c - Device handling events for example driver. 6 | 7 | Abstract: 8 | 9 | This file contains the device entry points and callbacks. 10 | 11 | Environment: 12 | 13 | Kernel-mode Driver Framework 14 | 15 | --*/ 16 | 17 | #include "Device.h" 18 | #include "U2F.h" 19 | #include "device.tmh" 20 | 21 | #ifdef ALLOC_PRAGMA 22 | #pragma alloc_text (PAGE, SoftU2FCreateDevice) 23 | #pragma alloc_text (PAGE, EvtDeviceSelfManagedIoInit) 24 | #pragma alloc_text (PAGE, EvtDeviceSelfManagedIoCleanup) 25 | #endif 26 | 27 | #pragma warning(disable: 4100) // unreferenced formal parameter 28 | #pragma warning(disable: 4244) // possible loss of data 29 | 30 | ULONG InstanceNo = 0; 31 | 32 | UCHAR SoftU2FHIDReportDescriptor[] = { 33 | 0x06, 0xD0, 0xF1, // Usage Page (Reserved 0xF1D0) 34 | 35 | 0x09, 0x01, // Usage (0x01) 36 | 37 | 0xA1, 0x01, // Collection (Application) 38 | 39 | 0x09, 0x20, // Usage (0x20) 40 | 41 | 0x15, 0x00, // Logical Minimum (0) 42 | 43 | 0x26, 0xFF, 0x00, // Logical Maximum (255) 44 | 45 | 0x75, 0x08, // Report Size (8) 46 | 47 | 0x95, 0x40, // Report Count (64) 48 | 49 | 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null 50 | 51 | // Position) 52 | 53 | 0x09, 0x21, // Usage (0x21) 54 | 55 | 0x15, 0x00, // Logical Minimum (0) 56 | 57 | 0x26, 0xFF, 0x00, // Logical Maximum (255) 58 | 59 | 0x75, 0x08, // Report Size (8) 60 | 61 | 0x95, 0x40, // Report Count (64) 62 | 63 | 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null 64 | // Position,Non-volatile) 65 | 0xC0, // End Collection 66 | 67 | }; 68 | 69 | NTSTATUS 70 | SoftU2FCreateDevice( 71 | _Inout_ PWDFDEVICE_INIT DeviceInit 72 | ) 73 | /*++ 74 | 75 | Routine Description: 76 | 77 | Worker routine called to create a device and its software resources. 78 | 79 | Arguments: 80 | 81 | DeviceInit - Pointer to an opaque init structure. Memory for this 82 | structure will be freed by the framework when the WdfDeviceCreate 83 | succeeds. So don't access the structure after that point. 84 | 85 | Return Value: 86 | 87 | NTSTATUS 88 | 89 | --*/ 90 | { 91 | WDF_OBJECT_ATTRIBUTES deviceAttributes; 92 | PDEVICE_CONTEXT deviceContext; 93 | VHF_CONFIG vhfConfig; 94 | WDFDEVICE device; 95 | NTSTATUS status; 96 | 97 | WDF_PNPPOWER_EVENT_CALLBACKS wdfPnpPowerCallbacks; 98 | 99 | PAGED_CODE(); 100 | 101 | WdfFdoInitSetFilter(DeviceInit); 102 | 103 | WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&wdfPnpPowerCallbacks); 104 | wdfPnpPowerCallbacks.EvtDeviceSelfManagedIoInit = EvtDeviceSelfManagedIoInit; 105 | wdfPnpPowerCallbacks.EvtDeviceSelfManagedIoCleanup = EvtDeviceSelfManagedIoCleanup; 106 | WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &wdfPnpPowerCallbacks); 107 | 108 | WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT); 109 | 110 | status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device); 111 | 112 | if (!NT_SUCCESS(status)) { 113 | return status; 114 | } 115 | 116 | deviceContext = GetDeviceContext(device); 117 | 118 | VHF_CONFIG_INIT(&vhfConfig, 119 | WdfDeviceWdmGetDeviceObject(device), 120 | sizeof(SoftU2FHIDReportDescriptor), 121 | SoftU2FHIDReportDescriptor); 122 | 123 | vhfConfig.VendorID = 0x08; 124 | vhfConfig.ProductID = 0x09; 125 | vhfConfig.VersionNumber = 1; 126 | vhfConfig.VhfClientContext = deviceContext; 127 | vhfConfig.EvtVhfAsyncOperationWriteReport = HidWriteInputReport; 128 | 129 | status = VhfCreate(&vhfConfig, &deviceContext->VhfHandle); 130 | 131 | if (!NT_SUCCESS(status)) 132 | { 133 | TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "VhfCreate failed %!STATUS!", status); 134 | return status; 135 | } 136 | 137 | KeInitializeSpinLock(&deviceContext->MessageProcessLock); 138 | 139 | status = CreateRawQueue(device, &deviceContext->RawQueue); 140 | if (!NT_SUCCESS(status)) 141 | { 142 | return status; 143 | } 144 | 145 | status = CreateManualQueue(device, &deviceContext->ManualQueue); 146 | 147 | if (!NT_SUCCESS(status)) 148 | { 149 | return status; 150 | } 151 | 152 | status = CreateTimer(device, &deviceContext->TimeoutMessageCleanupTimer); 153 | if (!NT_SUCCESS(status)) 154 | { 155 | return status; 156 | } 157 | 158 | status = CreateRawPdo(device); 159 | if (!NT_SUCCESS(status)) 160 | { 161 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Failed to create Raw Pdo\n")); 162 | return status; 163 | } 164 | 165 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Driver installed and loaded\n")); 166 | return status; 167 | } 168 | 169 | NTSTATUS 170 | EvtDeviceSelfManagedIoInit( 171 | WDFDEVICE WdfDevice 172 | ) 173 | { 174 | PDEVICE_CONTEXT deviceContext; 175 | NTSTATUS status; 176 | 177 | PAGED_CODE(); 178 | 179 | deviceContext = GetDeviceContext(WdfDevice); 180 | 181 | status = VhfStart(deviceContext->VhfHandle); 182 | 183 | if (!NT_SUCCESS(status)) 184 | { 185 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "VhfStart failed %d\n", status)); 186 | } 187 | 188 | return status; 189 | } 190 | 191 | VOID 192 | EvtDeviceSelfManagedIoCleanup( 193 | WDFDEVICE WdfDevice 194 | ) 195 | { 196 | PDEVICE_CONTEXT deviceContext; 197 | 198 | PAGED_CODE(); 199 | 200 | deviceContext = GetDeviceContext(WdfDevice); 201 | 202 | if (deviceContext->VhfHandle) 203 | { 204 | VhfDelete(deviceContext->VhfHandle, FALSE); 205 | } 206 | } 207 | 208 | NTSTATUS RAWPDO_EvtDeviceSelfManagedIoInit( 209 | _In_ WDFDEVICE Device 210 | ) 211 | { 212 | NTSTATUS status = STATUS_SUCCESS; 213 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "RawPdo started\n")); 214 | return status; 215 | } 216 | 217 | VOID 218 | HidWriteInputReport( 219 | _In_ PVOID VhfClientContext, 220 | _In_ VHFOPERATIONHANDLE VhfOperationHandle, 221 | _In_opt_ PVOID VhfOperationContext, 222 | _In_ PHID_XFER_PACKET HidTransferPacket 223 | ) 224 | { 225 | // this is where the application started to request U2F conversation 226 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "HidWriteInputReport called\n")); 227 | 228 | NTSTATUS status; 229 | PDEVICE_CONTEXT deviceContext = (PDEVICE_CONTEXT)VhfClientContext; 230 | KIRQL OldIrql; 231 | 232 | 233 | KeAcquireSpinLock(&deviceContext->MessageProcessLock, &OldIrql); 234 | 235 | HidMessageRead(deviceContext, HidTransferPacket); 236 | 237 | HidMessageHandle(deviceContext); 238 | 239 | KeReleaseSpinLock(&deviceContext->MessageProcessLock, OldIrql); 240 | 241 | status = VhfAsyncOperationComplete(VhfOperationHandle, STATUS_SUCCESS); 242 | 243 | if (!NT_SUCCESS(status)) 244 | { 245 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "WriteInput report respond failed with status %x\n", status)); 246 | } 247 | else { 248 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "WriteInput report responded\n")); 249 | } 250 | } 251 | 252 | VOID 253 | HidMessageRead( 254 | _In_ PDEVICE_CONTEXT deviceContext, 255 | _In_ PHID_XFER_PACKET HidTransferPacket 256 | ) 257 | { 258 | PU2FHID_MESSAGE message; 259 | UINT32 nData; 260 | PU2FHID_FRAME frame; 261 | PUCHAR data; 262 | 263 | frame = (PU2FHID_FRAME)HidTransferPacket->reportBuffer; 264 | 265 | if (frame->cid == 0x0) { 266 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Frame with cid 0.\n")); 267 | HidErrorMessageSend(deviceContext->VhfHandle, frame->cid, ERR_INVALID_CID); 268 | return; 269 | } 270 | 271 | message = MessageListFind(deviceContext, frame->cid); 272 | 273 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "MessageListFind result for cid: %d, %d. cmd: %d, MessateListCount: %d", frame->cid, !!message, frame->init.cmd, MessageListCount(deviceContext))); 274 | 275 | switch (FRAME_TYPE(*frame)) 276 | { 277 | case TYPE_INIT: 278 | if (message) { 279 | if (frame->init.cmd == U2FHID_INIT) { 280 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "U2FHID_INIT while waiting for CONT. Resetting.\n")); 281 | MessageListRemove(deviceContext, message); 282 | } 283 | else { 284 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "INIT frame out of order.Bailing.\n")); 285 | HidErrorMessageSend(deviceContext->VhfHandle, frame->cid, ERR_INVALID_SEQ); 286 | MessageListRemove(deviceContext, message); 287 | return; 288 | } 289 | } 290 | else if (frame->init.cmd == U2FHID_SYNC) { 291 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "SYNC frame out of order. Bailing.\n")); 292 | HidErrorMessageSend(deviceContext->VhfHandle, frame->cid, ERR_INVALID_CMD); 293 | return; 294 | } 295 | else if (frame->init.cmd != U2FHID_INIT && MessageListCount(deviceContext) > 0) 296 | { 297 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "INIT frame while waiting for CONT on other CID.\n")); 298 | HidErrorMessageSend(deviceContext->VhfHandle, frame->cid, ERR_CHANNEL_BUSY); 299 | return; 300 | } 301 | 302 | if (frame->cid == CID_BROADCAST && frame->init.cmd != U2FHID_INIT) 303 | { 304 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Non U2FHID_INIT message on broadcast CID.\n")); 305 | HidErrorMessageSend(deviceContext->VhfHandle, frame->cid, ERR_INVALID_CID); 306 | return; 307 | } 308 | 309 | 310 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "got frame from channel: %d\n", frame->cid)); 311 | 312 | message = MessageListCreate(deviceContext); 313 | if (!message) 314 | { 315 | return; 316 | } 317 | 318 | message->cmd = frame->init.cmd; 319 | message->cid = frame->cid; 320 | message->bcnt = MESSAGE_LEN(*frame); 321 | 322 | // magic number explanation 323 | // see maximum message length 324 | // https://fidoalliance.org/specs/fido-u2f-v1.0-ps-20141009/fido-u2f-hid-protocol-ps-20141009.html 325 | if (message->bcnt > MAX_BCNT) { 326 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "BCNT too large (%u). Bailing.\n", message->bcnt)); 327 | HidErrorMessageSend(deviceContext->VhfHandle, message->cid, ERR_INVALID_LEN); 328 | MessageListRemove(deviceContext, message); 329 | return; 330 | } 331 | 332 | message->buf = MmAllocateNonCachedMemory(message->bcnt); 333 | RtlZeroMemory(message->buf, message->bcnt); 334 | message->bufCap = message->bcnt; 335 | message->bufLen = 0; 336 | 337 | data = frame->init.data; 338 | 339 | if (message->bcnt > sizeof(frame->init.data)) 340 | { 341 | nData = sizeof(frame->init.data); 342 | } 343 | else 344 | { 345 | nData = message->bcnt; 346 | } 347 | 348 | break; 349 | 350 | case TYPE_CONT: 351 | if (!message) 352 | { 353 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "CONT frame out of order. Ignoring\n")); 354 | return; 355 | } 356 | 357 | if (FRAME_SEQ(*frame) != message->lastSeq++) 358 | { 359 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Bad SEQ in CONT frame (%d). Bailing\n", FRAME_SEQ(*frame))); 360 | MessageListRemove(deviceContext, message); 361 | HidErrorMessageSend(deviceContext->VhfHandle, frame->cid, ERR_INVALID_SEQ); 362 | return; 363 | } 364 | 365 | data = frame->cont.data; 366 | 367 | if (message->bufLen + sizeof(frame->cont.data) > message->bcnt) { 368 | nData = message->bcnt - (UINT16)message->bufLen; 369 | } 370 | else { 371 | nData = sizeof(frame->cont.data); 372 | } 373 | 374 | break; 375 | 376 | 377 | default: 378 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Unknow frame type: 0x%08x\n", FRAME_TYPE(*frame))); 379 | return; 380 | } 381 | 382 | 383 | for (UINT32 i = 0; i < nData; i++) 384 | { 385 | message->buf[message->bufLen + i] = data[i]; 386 | } 387 | message->bufLen += nData; 388 | 389 | } 390 | 391 | VOID 392 | HidMessageHandle( 393 | _In_ PDEVICE_CONTEXT deviceContext 394 | ) 395 | { 396 | PU2FHID_MESSAGE message; 397 | PU2FHID_MESSAGE nextMessage = deviceContext->MessageList; 398 | 399 | while (nextMessage) 400 | { 401 | message = nextMessage; 402 | nextMessage = message->next; 403 | 404 | if (HidMessageIsComplete(deviceContext, message)) 405 | { 406 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Message Complete, responding\n")); 407 | HidMessageFinalize(deviceContext, message); 408 | switch (message->cmd) 409 | { 410 | case U2FHID_INIT: 411 | U2FHandleMessageInit(deviceContext, message); 412 | break; 413 | case U2FHID_PING: 414 | U2FHandleMessagePing(deviceContext, message); 415 | break; 416 | case U2FHID_WINK: 417 | U2FHandleMessageWink(deviceContext, message); 418 | break; 419 | case U2FHID_SYNC: 420 | U2FHandleMessageSync(deviceContext, message); 421 | break; 422 | // all the above msg can be processed within kernel mode 423 | // the below msg, we need to forward to user space 424 | case U2FHID_MSG: 425 | U2FHandleMessageMsg(deviceContext, message); 426 | break; 427 | default: 428 | HidErrorMessageSend(deviceContext->VhfHandle, message->cid, ERR_INVALID_CMD); 429 | } 430 | MessageListRemove(deviceContext, message); 431 | } 432 | else if (HidMessageIsTimeout(deviceContext, message)) { 433 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Message Timeout, sending ERR_MSG_TIMEOUT.\n")); 434 | HidErrorMessageSend(deviceContext->VhfHandle, message->cid, ERR_MSG_TIMEOUT); 435 | MessageListRemove(deviceContext, message); 436 | } 437 | else { 438 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Message %d didn't complete, wainting for cont.\n", message->cid)); 439 | } 440 | } 441 | } 442 | 443 | #pragma region Message Handlers 444 | 445 | VOID 446 | U2FHandleMessageInit( 447 | _In_ PDEVICE_CONTEXT deviceContext, 448 | _In_ PU2FHID_MESSAGE message 449 | ) 450 | { 451 | NTSTATUS status; 452 | 453 | U2FHID_MESSAGE resp; 454 | U2FHID_INIT_RESP respData; 455 | PU2FHID_INIT_REQ reqData; 456 | 457 | RtlZeroMemory(&resp, sizeof(resp)); 458 | reqData = (PU2FHID_INIT_REQ)(message->data); 459 | 460 | resp.cmd = U2FHID_INIT; 461 | resp.bcnt = sizeof(U2FHID_INIT_RESP); 462 | 463 | if (message->cid == CID_BROADCAST) 464 | { 465 | resp.cid = CID_BROADCAST; 466 | respData.cid = ++deviceContext->cid; 467 | } 468 | else 469 | { 470 | resp.cid = message->cid; 471 | respData.cid = message->cid; 472 | } 473 | 474 | RtlCopyMemory(respData.nonce, reqData->nonce, INIT_NONCE_SIZE); 475 | respData.versionInterface = U2FHID_IF_VERSION; 476 | respData.versionMajor = 0; 477 | respData.versionMinor = 0; 478 | respData.versionBuild = 0; 479 | respData.capFlags = (0x0 | CAPFLAG_WINK); 480 | 481 | resp.data = message->data; 482 | RtlZeroMemory(resp.data, sizeof(respData)); 483 | RtlCopyMemory(resp.data, &respData, sizeof(respData)); 484 | 485 | status = HidMessageSend(deviceContext->VhfHandle, &resp); 486 | 487 | if (!NT_SUCCESS(status)) 488 | { 489 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Failed to submit report\n")); 490 | return; 491 | } 492 | } 493 | 494 | VOID 495 | U2FHandleMessagePing( 496 | _In_ PDEVICE_CONTEXT deviceContext, 497 | _In_ PU2FHID_MESSAGE message 498 | ) 499 | { 500 | U2FHID_MESSAGE resp; 501 | resp.cid = message->cid; 502 | resp.cmd = U2FHID_PING; 503 | resp.bcnt = message->bcnt; 504 | resp.data = message->data; 505 | 506 | HidMessageSend(deviceContext->VhfHandle, &resp); 507 | } 508 | 509 | 510 | VOID 511 | U2FHandleMessageWink( 512 | _In_ PDEVICE_CONTEXT deviceContext, 513 | _In_ PU2FHID_MESSAGE message 514 | ) 515 | { 516 | U2FHID_MESSAGE resp; 517 | 518 | resp.cid = message->cid; 519 | resp.cmd = U2FHID_WINK; 520 | resp.bcnt = message->bcnt; 521 | resp.data = message->data; 522 | 523 | HidMessageSend(deviceContext->VhfHandle, &resp); 524 | } 525 | 526 | VOID 527 | U2FHandleMessageSync( 528 | _In_ PDEVICE_CONTEXT deviceContext, 529 | _In_ PU2FHID_MESSAGE message 530 | ) 531 | { 532 | U2FHID_MESSAGE resp; 533 | 534 | resp.cid = message->cid; 535 | resp.cmd = U2FHID_SYNC; 536 | resp.bcnt = message->bcnt; 537 | resp.data = message->data; 538 | 539 | HidMessageSend(deviceContext->VhfHandle, message); 540 | } 541 | 542 | VOID 543 | U2FHandleMessageMsg( 544 | _In_ PDEVICE_CONTEXT deviceContext, 545 | _In_ PU2FHID_MESSAGE message 546 | ) 547 | { 548 | NTSTATUS status; 549 | WDFREQUEST notifyRequest; 550 | ULONG_PTR bytesTransferred = 0; 551 | PIO_CTL_XFER_MESSAGE xferMessage; 552 | 553 | // grap a pending inverted call request, so we use this to notify User Space 554 | status = WdfIoQueueRetrieveNextRequest(deviceContext->ManualQueue, ¬ifyRequest); 555 | 556 | if (!NT_SUCCESS(status)) 557 | { 558 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "No pending req found\n")); 559 | return; 560 | } 561 | 562 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Message bcnt %d\n", message->bcnt)); 563 | 564 | status = WdfRequestRetrieveOutputBuffer( 565 | notifyRequest, 566 | sizeof(IO_CTL_XFER_MESSAGE) + message->bcnt, 567 | (PVOID*)&xferMessage, 568 | NULL); 569 | if (!NT_SUCCESS(status)) 570 | { 571 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Cant retrive memory for request\n")); 572 | status = STATUS_MEMORY_NOT_ALLOCATED; 573 | goto FINISH; 574 | } 575 | 576 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Buffer retrived with len %d\n", sizeof(IO_CTL_XFER_MESSAGE) + message->bcnt)); 577 | 578 | xferMessage->cmd = message->cmd; 579 | xferMessage->cid = message->cid; 580 | xferMessage->bcnt = message->bcnt; 581 | RtlCopyMemory((PUCHAR)xferMessage + sizeof(IO_CTL_XFER_MESSAGE), message->data, message->bcnt); 582 | 583 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Respons built, sending\n")); 584 | 585 | bytesTransferred = sizeof(IO_CTL_XFER_MESSAGE) + message->bcnt; 586 | 587 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Sending response %d\n", bytesTransferred)); 588 | 589 | FINISH: 590 | WdfRequestCompleteWithInformation(notifyRequest, status, bytesTransferred); 591 | } 592 | 593 | #pragma endregion 594 | 595 | 596 | NTSTATUS 597 | CreateRawQueue( 598 | _In_ WDFDEVICE Device, 599 | _Out_ WDFQUEUE* Queue 600 | ) 601 | { 602 | NTSTATUS status; 603 | WDF_IO_QUEUE_CONFIG queueConfig; 604 | WDF_OBJECT_ATTRIBUTES queueAttributes; 605 | WDFQUEUE queue; 606 | PRAW_QUEUE_CONTEXT queueContext; 607 | 608 | WDF_IO_QUEUE_CONFIG_INIT( 609 | &queueConfig, 610 | WdfIoQueueDispatchParallel); 611 | 612 | queueConfig.EvtIoDeviceControl = EvtIoDeviceControlForMainPdo; 613 | 614 | WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE( 615 | &queueAttributes, 616 | RAW_QUEUE_CONTEXT); 617 | 618 | status = WdfIoQueueCreate( 619 | Device, 620 | &queueConfig, 621 | &queueAttributes, 622 | &queue); 623 | 624 | if (!NT_SUCCESS(status)) { 625 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "WdfIoQueueCreate failed 0x%X\n", status)); 626 | return status; 627 | } 628 | 629 | queueContext = GetRawQueueContext(queue); 630 | queueContext->Queue = queue; 631 | queueContext->DeviceContext = GetDeviceContext(Device); 632 | 633 | *Queue = queue; 634 | return status; 635 | } 636 | 637 | NTSTATUS 638 | CreateManualQueue( 639 | _In_ WDFDEVICE Device, 640 | _Out_ WDFQUEUE* Queue 641 | ) 642 | { 643 | NTSTATUS status; 644 | WDF_IO_QUEUE_CONFIG queueConfig; 645 | WDF_OBJECT_ATTRIBUTES queueAttributes; 646 | WDFQUEUE queue; 647 | PMANUAL_QUEUE_CONTEXT queueContext; 648 | 649 | WDF_IO_QUEUE_CONFIG_INIT( 650 | &queueConfig, 651 | WdfIoQueueDispatchManual 652 | ); 653 | 654 | WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE( 655 | &queueAttributes, 656 | MANUAL_QUEUE_CONTEXT 657 | ); 658 | 659 | status = WdfIoQueueCreate( 660 | Device, 661 | &queueConfig, 662 | &queueAttributes, 663 | &queue 664 | ); 665 | 666 | if (!NT_SUCCESS(status)) { 667 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "ManualQueue failed 0x%x\n", status)); 668 | return status; 669 | } 670 | 671 | queueContext = GetManualQueueContext(queue); 672 | queueContext->Queue = queue; 673 | queueContext->DeviceContext = GetDeviceContext(Device); 674 | 675 | *Queue = queue; 676 | 677 | return status; 678 | } 679 | 680 | NTSTATUS 681 | CreateTimer( 682 | _In_ WDFDEVICE Device, 683 | _Out_ WDFTIMER* Timer 684 | ) 685 | { 686 | WDF_TIMER_CONFIG timerConfig; 687 | WDF_OBJECT_ATTRIBUTES timerAttributes; 688 | NTSTATUS status; 689 | 690 | WDF_TIMER_CONFIG_INIT( 691 | &timerConfig, 692 | TimeoutMessagesCleanup 693 | ); 694 | 695 | timerConfig.AutomaticSerialization = TRUE; 696 | timerConfig.Period = 200; // 200 ms 697 | timerConfig.UseHighResolutionTimer = WdfTrue; 698 | 699 | WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes); 700 | timerAttributes.ParentObject = Device; 701 | 702 | status = WdfTimerCreate( 703 | &timerConfig, 704 | &timerAttributes, 705 | Timer 706 | ); 707 | 708 | if (!NT_SUCCESS(status)) 709 | { 710 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Create timer failed")); 711 | return status; 712 | } 713 | 714 | WdfTimerStart(*Timer, WDF_REL_TIMEOUT_IN_MS(100)); 715 | return status; 716 | } 717 | 718 | NTSTATUS 719 | CreateRawPdo( 720 | _In_ WDFDEVICE Device 721 | ) 722 | { 723 | NTSTATUS status; 724 | PWDFDEVICE_INIT pDeviceInit = NULL; 725 | PRAWPDO_DEVICE_CONTEXT pdoData = NULL; 726 | WDFDEVICE hChild = NULL; 727 | WDF_OBJECT_ATTRIBUTES pdoAttributes; 728 | WDF_DEVICE_PNP_CAPABILITIES pnpCaps; 729 | WDF_IO_QUEUE_CONFIG ioQueueConfig; 730 | WDFQUEUE queue; 731 | WDF_DEVICE_STATE deviceState; 732 | PDEVICE_CONTEXT deviceContext; 733 | WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks; 734 | 735 | DECLARE_CONST_UNICODE_STRING(deviceId, SoftU2F_DEVICE_ID); 736 | DECLARE_CONST_UNICODE_STRING(deviceLocation, L"SoftU2F Communicator\0"); 737 | DECLARE_CONST_UNICODE_STRING(SDDL_MY_PERMISSIONS, L"D:P(A;; GA;;; SY)(A;; GA;;; BA)(A;; GA;;; WD)"); 738 | DECLARE_UNICODE_STRING_SIZE(buffer, MAX_ID_LEN); 739 | 740 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Creating RawPdo\n")); 741 | 742 | pDeviceInit = WdfPdoInitAllocate(Device); 743 | 744 | if (pDeviceInit == NULL) { 745 | status = STATUS_INSUFFICIENT_RESOURCES; 746 | goto Cleanup; 747 | } 748 | 749 | WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks); 750 | pnpPowerCallbacks.EvtDeviceSelfManagedIoInit = RAWPDO_EvtDeviceSelfManagedIoInit; 751 | WdfDeviceInitSetPnpPowerEventCallbacks( 752 | pDeviceInit, 753 | &pnpPowerCallbacks 754 | ); 755 | 756 | // 757 | // Mark the device RAW so that the child device can be started 758 | // and accessed without requiring a function driver. Since we are 759 | // creating a RAW PDO, we must provide a class guid. 760 | // 761 | status = WdfPdoInitAssignRawDevice(pDeviceInit, &GUID_DEVCLASS_SoftU2F); 762 | if (!NT_SUCCESS(status)) { 763 | goto Cleanup; 764 | } 765 | 766 | 767 | // 768 | // Since keyboard is secure device, we must protect ourselves from random 769 | // users sending ioctls and creating trouble. 770 | // 771 | status = WdfDeviceInitAssignSDDLString(pDeviceInit, 772 | &SDDL_MY_PERMISSIONS); 773 | if (!NT_SUCCESS(status)) { 774 | goto Cleanup; 775 | } 776 | 777 | // 778 | // Assign DeviceID - This will be reported to IRP_MN_QUERY_ID/BusQueryDeviceID 779 | // 780 | status = WdfPdoInitAssignDeviceID(pDeviceInit, &deviceId); 781 | if (!NT_SUCCESS(status)) { 782 | goto Cleanup; 783 | } 784 | 785 | // 786 | // We could be enumerating more than one children if the filter attaches 787 | // to multiple instances of keyboard, so we must provide a 788 | // BusQueryInstanceID. If we don't, system will throw CA bug check. 789 | // 790 | status = RtlUnicodeStringPrintf(&buffer, L"%02d", InstanceNo); 791 | if (!NT_SUCCESS(status)) { 792 | goto Cleanup; 793 | } 794 | 795 | status = WdfPdoInitAssignInstanceID(pDeviceInit, &buffer); 796 | if (!NT_SUCCESS(status)) { 797 | goto Cleanup; 798 | } 799 | 800 | 801 | // 802 | // Provide a description about the device. This text is usually read from 803 | // the device. In the case of USB device, this text comes from the string 804 | // descriptor. This text is displayed momentarily by the PnP manager while 805 | // it's looking for a matching INF. If it finds one, it uses the Device 806 | // Description from the INF file to display in the device manager. 807 | // Since our device is raw device and we don't provide any hardware ID 808 | // to match with an INF, this text will be displayed in the device manager. 809 | // 810 | status = RtlUnicodeStringPrintf(&buffer, L"SoftU2F_Filter_Pdo_%02d", InstanceNo); 811 | if (!NT_SUCCESS(status)) { 812 | goto Cleanup; 813 | } 814 | InstanceNo++; 815 | 816 | // 817 | // You can call WdfPdoInitAddDeviceText multiple times, adding device 818 | // text for multiple locales. When the system displays the text, it 819 | // chooses the text that matches the current locale, if available. 820 | // Otherwise it will use the string for the default locale. 821 | // The driver can specify the driver's default locale by calling 822 | // WdfPdoInitSetDefaultLocale. 823 | // 824 | status = WdfPdoInitAddDeviceText(pDeviceInit, 825 | &buffer, 826 | &deviceLocation, 827 | 0x409 828 | ); 829 | 830 | if (!NT_SUCCESS(status)) { 831 | goto Cleanup; 832 | } 833 | 834 | WdfPdoInitSetDefaultLocale(pDeviceInit, 0x409); 835 | 836 | // 837 | // Initialize the attributes to specify the size of PDO device extension. 838 | // All the state information private to the PDO will be tracked here. 839 | // 840 | WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&pdoAttributes, RAWPDO_DEVICE_CONTEXT); 841 | 842 | // 843 | // Set up our queue to allow forwarding of requests to the parent 844 | // This is done so that the cached Keyboard Attributes can be retrieved 845 | // 846 | WdfPdoInitAllowForwardingRequestToParent(pDeviceInit); 847 | 848 | status = WdfDeviceCreate(&pDeviceInit, &pdoAttributes, &hChild); 849 | if (!NT_SUCCESS(status)) { 850 | goto Cleanup; 851 | } 852 | 853 | // 854 | // Get the device context. 855 | // 856 | pdoData = GetRawPdoDeviceContext(hChild); 857 | 858 | pdoData->InstanceNo = InstanceNo; 859 | 860 | 861 | // 862 | // Get the parent queue we will be forwarding to 863 | // 864 | deviceContext = GetDeviceContext(Device); 865 | pdoData->ParentQueue = deviceContext->RawQueue; 866 | 867 | // 868 | // Configure the default queue associated with the control device object 869 | // to be Serial so that request passed to EvtIoDeviceControl are serialized. 870 | // A default queue gets all the requests that are not 871 | // configure-fowarded using WdfDeviceConfigureRequestDispatching. 872 | // 873 | WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&ioQueueConfig, 874 | WdfIoQueueDispatchSequential); 875 | 876 | ioQueueConfig.EvtIoDeviceControl = EvtIoDeviceControlForRawPdo; 877 | 878 | 879 | status = WdfIoQueueCreate(hChild, 880 | &ioQueueConfig, 881 | WDF_NO_OBJECT_ATTRIBUTES, 882 | &queue // pointer to default queue 883 | ); 884 | if (!NT_SUCCESS(status)) { 885 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "WdfIoQueueCreate failed 0x%x\n", status)); 886 | goto Cleanup; 887 | } 888 | 889 | // 890 | // Set some properties for the child device. 891 | // 892 | WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCaps); 893 | 894 | pnpCaps.Removable = WdfTrue; 895 | pnpCaps.SurpriseRemovalOK = WdfTrue; 896 | pnpCaps.NoDisplayInUI = WdfTrue; 897 | 898 | pnpCaps.Address = InstanceNo; 899 | pnpCaps.UINumber = InstanceNo; 900 | 901 | WdfDeviceSetPnpCapabilities(hChild, &pnpCaps); 902 | 903 | // 904 | // TODO: In addition to setting NoDisplayInUI in DeviceCaps, we 905 | // have to do the following to hide the device. Following call 906 | // tells the framework to report the device state in 907 | // IRP_MN_QUERY_DEVICE_STATE request. 908 | // 909 | WDF_DEVICE_STATE_INIT(&deviceState); 910 | deviceState.DontDisplayInUI = WdfTrue; 911 | WdfDeviceSetDeviceState(hChild, &deviceState); 912 | 913 | // 914 | // Tell the Framework that this device will need an interface so that 915 | // application can find our device and talk to it. 916 | // 917 | status = WdfDeviceCreateDeviceInterface( 918 | hChild, 919 | &GUID_DEVINTERFACE_SOFTU2F_FILTER, 920 | NULL 921 | ); 922 | 923 | if (!NT_SUCCESS(status)) { 924 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "WdfDeviceCreateDeviceInterface failed 0x%x\n", status)); 925 | goto Cleanup; 926 | } 927 | 928 | // 929 | // Add this device to the FDO's collection of children. 930 | // After the child device is added to the static collection successfully, 931 | // driver must call WdfPdoMarkMissing to get the device deleted. It 932 | // shouldn't delete the child device directly by calling WdfObjectDelete. 933 | // 934 | status = WdfFdoAddStaticChild(Device, hChild); 935 | if (!NT_SUCCESS(status)) { 936 | goto Cleanup; 937 | } 938 | deviceContext->RawPdo = hChild; 939 | 940 | 941 | // 942 | // pDeviceInit will be freed by WDF. 943 | // 944 | return STATUS_SUCCESS; 945 | Cleanup: 946 | 947 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "CreateRawPdo failed %x\n", status)); 948 | 949 | // 950 | // Call WdfDeviceInitFree if you encounter an error while initializing 951 | // a new framework device object. If you call WdfDeviceInitFree, 952 | // do not call WdfDeviceCreate. 953 | // 954 | if (pDeviceInit != NULL) { 955 | WdfDeviceInitFree(pDeviceInit); 956 | } 957 | 958 | if (hChild) { 959 | WdfObjectDelete(hChild); 960 | } 961 | 962 | return status; 963 | } 964 | 965 | NTSTATUS 966 | HidErrorMessageSend( 967 | _In_ 968 | VHFHANDLE VhfHandle, 969 | _In_ 970 | UINT32 cid, 971 | UINT8 code 972 | ) 973 | { 974 | U2FHID_MESSAGE message = { 0 }; 975 | UINT8 data[1]; 976 | data[0] = code; 977 | RtlZeroMemory(&message, sizeof(U2FHID_MESSAGE)); 978 | message.cmd = U2FHID_ERROR; 979 | message.cid = cid; 980 | message.bcnt = 1; 981 | message.data = (PUCHAR)& data[0]; 982 | 983 | return HidMessageSend(VhfHandle, &message); 984 | 985 | } 986 | 987 | NTSTATUS 988 | HidMessageSend( 989 | _In_ VHFHANDLE VhfHandle, 990 | _In_ PU2FHID_MESSAGE message 991 | ) 992 | { 993 | NTSTATUS status; 994 | PUINT8 src; 995 | PUINT8 srcEnd; 996 | PUINT8 dst; 997 | PUINT8 dstEnd; 998 | UINT8 seq = 0x00; 999 | U2FHID_FRAME frame; 1000 | HID_XFER_PACKET HidXferPacket; 1001 | LARGE_INTEGER delayInterval; 1002 | 1003 | RtlZeroMemory(&frame, HID_RPT_SIZE); 1004 | 1005 | frame.cid = message->cid; 1006 | frame.type |= TYPE_INIT; 1007 | frame.init.cmd |= message->cmd; 1008 | frame.init.bcnth = message->bcnt >> 8; 1009 | frame.init.bcntl = message->bcnt & 0xff; 1010 | 1011 | src = message->data; 1012 | srcEnd = src + message->bcnt; 1013 | dst = frame.init.data; 1014 | dstEnd = dst + sizeof(frame.init.data); 1015 | 1016 | while (1) { 1017 | if (srcEnd - src > dstEnd - dst) { 1018 | RtlCopyMemory(dst, src, dstEnd - dst); 1019 | src += dstEnd - dst; 1020 | } 1021 | else { 1022 | RtlCopyMemory(dst, src, srcEnd - src); 1023 | src += srcEnd - src; 1024 | } 1025 | 1026 | HidXferPacket.reportBuffer = (PUCHAR)& frame; 1027 | HidXferPacket.reportBufferLen = sizeof(frame); 1028 | HidXferPacket.reportId = 0; 1029 | 1030 | status = VhfReadReportSubmit(VhfHandle, &HidXferPacket); 1031 | 1032 | if (!NT_SUCCESS(status)) 1033 | { 1034 | return status; 1035 | } 1036 | 1037 | if (src >= srcEnd) { 1038 | break; 1039 | } 1040 | 1041 | delayInterval.QuadPart = 1 * RELATIVE_MILLISECOND; 1042 | KeDelayExecutionThread(KernelMode, FALSE, &delayInterval); 1043 | 1044 | dst = frame.cont.data; 1045 | dstEnd = dst + sizeof(frame.cont.data); 1046 | frame.cont.seq = seq++; 1047 | RtlZeroMemory(frame.cont.data, sizeof(frame.cont.data)); 1048 | } 1049 | 1050 | return status; 1051 | } 1052 | 1053 | #pragma region Message List Operations 1054 | 1055 | PU2FHID_MESSAGE 1056 | MessageListFind( 1057 | _In_ PDEVICE_CONTEXT deviceContext, 1058 | _In_ UINT32 cid 1059 | ) 1060 | { 1061 | PU2FHID_MESSAGE msg = deviceContext->MessageList; 1062 | 1063 | while (msg) 1064 | { 1065 | if (msg->cid == cid) { 1066 | break; 1067 | } 1068 | 1069 | msg = msg->next; 1070 | } 1071 | 1072 | return msg; 1073 | } 1074 | 1075 | VOID 1076 | MessageListRemove( 1077 | _In_ PDEVICE_CONTEXT deviceContext, 1078 | _In_ PU2FHID_MESSAGE message 1079 | ) 1080 | { 1081 | PU2FHID_MESSAGE previous; 1082 | 1083 | if (message == deviceContext->MessageList) { 1084 | deviceContext->MessageList = message->next; 1085 | MessageFree(message); 1086 | return; 1087 | } 1088 | 1089 | previous = deviceContext->MessageList; 1090 | while (previous && previous->next != message) { 1091 | previous = previous->next; 1092 | } 1093 | 1094 | if (!previous) { 1095 | return; 1096 | } 1097 | 1098 | previous->next = message->next; 1099 | MessageFree(message); 1100 | } 1101 | 1102 | PU2FHID_MESSAGE 1103 | MessageListCreate( 1104 | _In_ PDEVICE_CONTEXT deviceContext 1105 | ) 1106 | { 1107 | PU2FHID_MESSAGE message; 1108 | PU2FHID_MESSAGE lastMessage = NULL; 1109 | 1110 | message = MessageAlloc(deviceContext); 1111 | if (!message) 1112 | { 1113 | return NULL; 1114 | } 1115 | 1116 | if (!deviceContext->MessageList) { 1117 | deviceContext->MessageList = message; 1118 | return message; 1119 | } 1120 | 1121 | lastMessage = deviceContext->MessageList; 1122 | while (lastMessage->next) { 1123 | lastMessage = lastMessage->next; 1124 | } 1125 | lastMessage->next = message; 1126 | return message; 1127 | } 1128 | 1129 | PU2FHID_MESSAGE 1130 | MessageAlloc 1131 | ( 1132 | _In_ PDEVICE_CONTEXT deviceContext 1133 | ) 1134 | { 1135 | PU2FHID_MESSAGE message; 1136 | 1137 | message = (PU2FHID_MESSAGE)MmAllocateNonCachedMemory(sizeof(U2FHID_MESSAGE)); 1138 | if (!message) 1139 | { 1140 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "No memory for new message.\n")); 1141 | return NULL; 1142 | } 1143 | 1144 | RtlZeroMemory(message, sizeof(U2FHID_MESSAGE)); 1145 | KeQueryTickCount(&message->createdAtTicks); 1146 | return message; 1147 | } 1148 | 1149 | UINT32 1150 | MessageListCount( 1151 | _In_ PDEVICE_CONTEXT deviceContext 1152 | ) 1153 | { 1154 | PU2FHID_MESSAGE msg = deviceContext->MessageList; 1155 | UINT32 count = 0; 1156 | 1157 | while (msg) { 1158 | count++; 1159 | msg = msg->next; 1160 | } 1161 | 1162 | return count; 1163 | } 1164 | 1165 | VOID 1166 | MessageFree( 1167 | _In_ PU2FHID_MESSAGE message 1168 | ) 1169 | { 1170 | if (message) { 1171 | if (message->data) 1172 | { 1173 | MmFreeNonCachedMemory(message->data, sizeof(message->bcnt)); 1174 | } 1175 | if (message->buf) 1176 | { 1177 | MmFreeNonCachedMemory(message->buf, sizeof(message->bufCap)); 1178 | } 1179 | MmFreeNonCachedMemory(message, sizeof(U2FHID_MESSAGE)); 1180 | } 1181 | } 1182 | 1183 | BOOLEAN 1184 | HidMessageIsComplete( 1185 | _In_ PDEVICE_CONTEXT deviceContext, 1186 | _In_ PU2FHID_MESSAGE message 1187 | ) 1188 | { 1189 | if (message) 1190 | { 1191 | return message->bufLen == message->bcnt; 1192 | } 1193 | return FALSE; 1194 | } 1195 | 1196 | 1197 | BOOLEAN 1198 | HidMessageIsTimeout( 1199 | _In_ PDEVICE_CONTEXT deviceContext, 1200 | _In_ PU2FHID_MESSAGE message 1201 | ) 1202 | { 1203 | // TODO: refactor please 1204 | LARGE_INTEGER currentTimestampTicks; 1205 | ULONG tickIncrement, nsToTimeout; 1206 | LONGLONG ticksDelta, nsElapsed; 1207 | 1208 | tickIncrement = KeQueryTimeIncrement(); 1209 | nsToTimeout = 0.5 * 1000000000L; // 0.5s 1210 | 1211 | KeQueryTickCount(¤tTimestampTicks); 1212 | 1213 | ticksDelta = currentTimestampTicks.QuadPart - message->createdAtTicks.QuadPart; 1214 | 1215 | nsElapsed = ticksDelta * tickIncrement * 100; 1216 | 1217 | return nsElapsed > nsToTimeout; 1218 | } 1219 | 1220 | VOID 1221 | HidMessageFinalize( 1222 | _In_ PDEVICE_CONTEXT deviceContext, 1223 | _In_ PU2FHID_MESSAGE message 1224 | ) 1225 | { 1226 | message->data = MmAllocateNonCachedMemory(message->bufCap); 1227 | RtlZeroMemory(message->data, message->bufCap); 1228 | 1229 | RtlCopyMemory(message->data, message->buf, message->bufCap); 1230 | message->bcnt = message->bufCap; 1231 | 1232 | if (message->buf) { 1233 | // in the case that WINK is sending zero length data 1234 | // so buf is 0/NULL 1235 | MmFreeNonCachedMemory(message->buf, message->bufCap); 1236 | message->buf = NULL; 1237 | message->bufCap = 0; 1238 | } 1239 | } 1240 | 1241 | VOID 1242 | TimeoutMessagesCleanup( 1243 | WDFTIMER Timer 1244 | ) 1245 | { 1246 | WDFDEVICE device; 1247 | PDEVICE_CONTEXT deviceContext; 1248 | KIRQL OldIrql; 1249 | 1250 | device = (WDFDEVICE)WdfTimerGetParentObject(Timer); 1251 | deviceContext = GetDeviceContext(device); 1252 | 1253 | KeAcquireSpinLock(&deviceContext->MessageProcessLock, &OldIrql); 1254 | 1255 | HidMessageHandle(deviceContext); 1256 | 1257 | KeReleaseSpinLock(&deviceContext->MessageProcessLock, OldIrql); 1258 | } 1259 | 1260 | VOID 1261 | EvtIoDeviceControlForRawPdo( 1262 | _In_ 1263 | WDFQUEUE Queue, 1264 | _In_ 1265 | WDFREQUEST Request, 1266 | _In_ 1267 | size_t OutputBufferLength, 1268 | _In_ 1269 | size_t InputBufferLength, 1270 | _In_ 1271 | ULONG IoControlCode 1272 | ) 1273 | { 1274 | NTSTATUS status = STATUS_SUCCESS; 1275 | WDFDEVICE parent = WdfIoQueueGetDevice(Queue); 1276 | PRAWPDO_DEVICE_CONTEXT pdoContext; 1277 | WDF_REQUEST_FORWARD_OPTIONS forwardOptions; 1278 | UNREFERENCED_PARAMETER(OutputBufferLength); 1279 | UNREFERENCED_PARAMETER(InputBufferLength); 1280 | 1281 | pdoContext = GetRawPdoDeviceContext(parent); 1282 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "RawPdo got DeviceControl, ControlCode(%lu)\n", IoControlCode)); 1283 | 1284 | switch (IoControlCode) 1285 | { 1286 | case IOCTL_SOFTU2F_FILTER_INIT: 1287 | case IOCTL_SOFTU2F_FILTER_WRITE_DATA: 1288 | WDF_REQUEST_FORWARD_OPTIONS_INIT(&forwardOptions); 1289 | status = WdfRequestForwardToParentDeviceIoQueue(Request, pdoContext->ParentQueue, &forwardOptions); 1290 | if (!NT_SUCCESS(status)) 1291 | { 1292 | WdfRequestComplete(Request, STATUS_DEVICE_BUSY); 1293 | } 1294 | break; 1295 | default: 1296 | WdfRequestComplete(Request, STATUS_NOT_IMPLEMENTED); 1297 | } 1298 | } 1299 | 1300 | VOID 1301 | EvtIoDeviceControlForMainPdo( 1302 | _In_ 1303 | WDFQUEUE Queue, 1304 | _In_ 1305 | WDFREQUEST Request, 1306 | _In_ 1307 | size_t OutputBufferLength, 1308 | _In_ 1309 | size_t InputBufferLength, 1310 | _In_ 1311 | ULONG IoControlCode 1312 | ) 1313 | { 1314 | NTSTATUS status = STATUS_SUCCESS; 1315 | WDFDEVICE device; 1316 | PDEVICE_CONTEXT deviceContext; 1317 | ULONG_PTR nData = 0; 1318 | PVOID requestInputBuffer; 1319 | PIO_CTL_XFER_MESSAGE xferMessage; 1320 | U2FHID_MESSAGE response; 1321 | 1322 | const size_t IO_CTL_XFER_MESSAGE_SIZE = MAX_BCNT + sizeof(IO_CTL_XFER_MESSAGE); 1323 | 1324 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Entered EvtIoDeviceControlForMainPdo, IoControlCode(%lu), InputBufferLength %d, OutputBufferLength %d\n", IoControlCode, InputBufferLength, OutputBufferLength)); 1325 | 1326 | device = WdfIoQueueGetDevice(Queue); 1327 | deviceContext = GetDeviceContext(device); 1328 | 1329 | switch (IoControlCode) 1330 | { 1331 | case IOCTL_SOFTU2F_FILTER_INIT: 1332 | if (OutputBufferLength < IO_CTL_XFER_MESSAGE_SIZE) 1333 | { 1334 | status = STATUS_BUFFER_TOO_SMALL; 1335 | nData = IO_CTL_XFER_MESSAGE_SIZE; 1336 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "OutputBuffer too small, required: %lu\n", nData)); 1337 | break; 1338 | } 1339 | 1340 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "BufferLen check passed %d, forwarding to manual queue\n", OutputBufferLength)); 1341 | 1342 | // supply the req to ManualQueue, so when a message received from kernel, the inverted call can use this request handle to notify user space. 1343 | status = WdfRequestForwardToIoQueue(Request, deviceContext->ManualQueue); 1344 | if (!NT_SUCCESS(status)) 1345 | { 1346 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Failed to forward to manual queue\n")); 1347 | break; 1348 | } 1349 | 1350 | // to see if User Space replied with anything 1351 | if (InputBufferLength >= sizeof(IO_CTL_XFER_MESSAGE) && InputBufferLength <= IO_CTL_XFER_MESSAGE_SIZE) 1352 | { 1353 | status = WdfRequestRetrieveInputBuffer(Request, InputBufferLength, &requestInputBuffer, NULL); 1354 | if (!NT_SUCCESS(status)) 1355 | { 1356 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Failed to retrive inputBuffer, NT_STATUS: %d, InputBufferLength: %d\n", status, InputBufferLength)); 1357 | return; 1358 | } 1359 | 1360 | xferMessage = (PIO_CTL_XFER_MESSAGE)requestInputBuffer; 1361 | 1362 | if (xferMessage->bcnt > InputBufferLength - sizeof(IO_CTL_XFER_MESSAGE)) { 1363 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "message bcnt is out of bounds: %d\n", xferMessage->bcnt)); 1364 | return; 1365 | } 1366 | 1367 | response.cmd = xferMessage->cmd; 1368 | response.cid = xferMessage->cid; 1369 | response.bcnt = xferMessage->bcnt; 1370 | response.data = (PUCHAR)xferMessage + sizeof(IO_CTL_XFER_MESSAGE); 1371 | 1372 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Response message built, sending cmd: %d, cid: %d, bcnt: %d\n", response.cmd, response.cid, response.bcnt)); 1373 | 1374 | // if we got a response from User Space, submit it to HID VHF; 1375 | status = HidMessageSend(deviceContext->VhfHandle, &response); 1376 | if (!NT_SUCCESS(status)) 1377 | { 1378 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Response message send failed\n")); 1379 | } 1380 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Response message sent\n")); 1381 | 1382 | } 1383 | else 1384 | { 1385 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Invalid InputBufferLength %d \n", InputBufferLength)); 1386 | } 1387 | 1388 | return; 1389 | 1390 | default: 1391 | status = STATUS_NOT_IMPLEMENTED; 1392 | break; 1393 | } 1394 | 1395 | WdfRequestCompleteWithInformation(Request, status, nData); 1396 | } 1397 | #pragma endregion 1398 | -------------------------------------------------------------------------------- /SoftU2FDriver/Device.h: -------------------------------------------------------------------------------- 1 | /*++ 2 | 3 | Module Name: 4 | 5 | device.h 6 | 7 | Abstract: 8 | 9 | This file contains the device definitions. 10 | 11 | Environment: 12 | 13 | Kernel-mode Driver Framework 14 | 15 | --*/ 16 | #pragma once 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "U2F.h" 25 | #include "Public.h" 26 | #include "trace.h" 27 | 28 | 29 | 30 | EXTERN_C_START 31 | 32 | EVT_WDF_TIMER TimeoutMessagesCleanup; 33 | 34 | EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT EvtDeviceSelfManagedIoInit; 35 | EVT_WDF_DEVICE_SELF_MANAGED_IO_CLEANUP EvtDeviceSelfManagedIoCleanup; 36 | 37 | EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT RAWPDO_EvtDeviceSelfManagedIoInit; 38 | EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL EvtIoDeviceControlForRawPdo; 39 | EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL EvtIoDeviceControlForMainPdo; 40 | 41 | EVT_VHF_CLEANUP VhfSourceDeviceCleanup; 42 | EVT_VHF_ASYNC_OPERATION HidWriteInputReport; 43 | 44 | // 45 | // The device context performs the same job as 46 | // a WDM device extension in the driver frameworks 47 | // 48 | typedef struct _DEVICE_CONTEXT 49 | { 50 | VHFHANDLE VhfHandle; 51 | 52 | UINT32 cid; 53 | PU2FHID_MESSAGE MessageList; 54 | KSPIN_LOCK MessageProcessLock; 55 | WDFTIMER TimeoutMessageCleanupTimer; 56 | 57 | WDFDEVICE RawPdo; 58 | WDFQUEUE RawQueue; 59 | 60 | WDFQUEUE ManualQueue; 61 | 62 | } DEVICE_CONTEXT, *PDEVICE_CONTEXT; 63 | 64 | // 65 | // This macro will generate an inline function called DeviceGetContext 66 | // which will be used to get a pointer to the device context memory 67 | // in a type safe manner. 68 | // 69 | WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DEVICE_CONTEXT, GetDeviceContext) 70 | 71 | typedef struct _QUEUE_CONTEXT 72 | { 73 | WDFQUEUE Queue; 74 | PDEVICE_CONTEXT DeviceContext; 75 | 76 | } RAW_QUEUE_CONTEXT, *PRAW_QUEUE_CONTEXT; 77 | 78 | WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(RAW_QUEUE_CONTEXT, GetRawQueueContext); 79 | 80 | typedef struct _MANUAL_QUEUE_CONTEXT 81 | { 82 | WDFQUEUE Queue; 83 | PDEVICE_CONTEXT DeviceContext; 84 | 85 | } MANUAL_QUEUE_CONTEXT, * PMANUAL_QUEUE_CONTEXT; 86 | WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(MANUAL_QUEUE_CONTEXT, GetManualQueueContext); 87 | 88 | // Raw PDO context. 89 | typedef struct _RAWPDO_DEVICE_CONTEXT 90 | { 91 | 92 | // TODO; is this used 93 | ULONG InstanceNo; 94 | 95 | // 96 | // Queue of the parent device we will forward requests to 97 | // 98 | WDFQUEUE ParentQueue; 99 | 100 | } RAWPDO_DEVICE_CONTEXT, * PRAWPDO_DEVICE_CONTEXT; 101 | WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(RAWPDO_DEVICE_CONTEXT, GetRawPdoDeviceContext) 102 | 103 | 104 | NTSTATUS 105 | CreateRawQueue( 106 | _In_ WDFDEVICE Device, 107 | _Out_ WDFQUEUE *Queue 108 | ); 109 | 110 | NTSTATUS 111 | CreateManualQueue( 112 | _In_ WDFDEVICE Device, 113 | _Out_ WDFQUEUE* Queue 114 | ); 115 | 116 | NTSTATUS 117 | CreateTimer( 118 | _In_ WDFDEVICE Device, 119 | _Out_ WDFTIMER* Timer 120 | ); 121 | 122 | NTSTATUS 123 | CreateRawPdo( 124 | _In_ WDFDEVICE Device 125 | ); 126 | 127 | // 128 | // Function to initialize the device and its callbacks 129 | // 130 | NTSTATUS 131 | SoftU2FCreateDevice( 132 | _Inout_ PWDFDEVICE_INIT DeviceInit 133 | ); 134 | 135 | 136 | #pragma region U2FHID 137 | 138 | NTSTATUS 139 | HidErrorMessageSend( 140 | _In_ 141 | VHFHANDLE VhfHandle, 142 | _In_ 143 | UINT32 cid, 144 | UINT8 code 145 | ); 146 | 147 | VOID 148 | HidMessageRead( 149 | _In_ PDEVICE_CONTEXT deviceContext, 150 | _In_ PHID_XFER_PACKET HidTransferPacket 151 | ); 152 | 153 | VOID 154 | HidMessageHandle( 155 | _In_ PDEVICE_CONTEXT deviceContext 156 | ); 157 | 158 | NTSTATUS 159 | HidMessageSend( 160 | _In_ VHFHANDLE VhfHandle, 161 | _In_ PU2FHID_MESSAGE message 162 | ); 163 | 164 | BOOLEAN 165 | HidMessageIsTimeout( 166 | _In_ PDEVICE_CONTEXT deviceContext, 167 | _In_ PU2FHID_MESSAGE message 168 | ); 169 | 170 | #pragma endregion 171 | 172 | 173 | #pragma region MessageList 174 | 175 | PU2FHID_MESSAGE 176 | MessageListFind( 177 | _In_ PDEVICE_CONTEXT deviceContext, 178 | _In_ UINT32 cid 179 | ); 180 | 181 | UINT32 182 | MessageListCount( 183 | _In_ PDEVICE_CONTEXT deviceContext 184 | ); 185 | 186 | VOID 187 | MessageListRemove( 188 | _In_ PDEVICE_CONTEXT deviceContext, 189 | _In_ PU2FHID_MESSAGE message 190 | ); 191 | 192 | PU2FHID_MESSAGE 193 | MessageListCreate( 194 | _In_ PDEVICE_CONTEXT deviceContext 195 | ); 196 | 197 | VOID 198 | MessageFree( 199 | _In_ PU2FHID_MESSAGE message 200 | ); 201 | 202 | PU2FHID_MESSAGE 203 | MessageAlloc 204 | ( 205 | _In_ PDEVICE_CONTEXT deviceContext 206 | ); 207 | 208 | VOID 209 | HidMessageFinalize( 210 | _In_ PDEVICE_CONTEXT deviceContext, 211 | _In_ PU2FHID_MESSAGE message 212 | ); 213 | 214 | BOOLEAN 215 | HidMessageIsComplete( 216 | _In_ PDEVICE_CONTEXT deviceContext, 217 | _In_ PU2FHID_MESSAGE message 218 | ); 219 | 220 | #pragma endregion 221 | 222 | #pragma region Message Handlers 223 | 224 | typedef VOID U2FMessageHandler( 225 | _In_ PDEVICE_CONTEXT deviceContext, 226 | _In_ PU2FHID_MESSAGE message 227 | ); 228 | 229 | U2FMessageHandler U2FHandleMessageInit; 230 | 231 | U2FMessageHandler U2FHandleMessagePing; 232 | 233 | U2FMessageHandler U2FHandleMessageWink; 234 | 235 | U2FMessageHandler U2FHandleMessageSync; 236 | 237 | U2FMessageHandler U2FHandleMessageMsg; 238 | 239 | #pragma endregion 240 | 241 | EXTERN_C_END 242 | -------------------------------------------------------------------------------- /SoftU2FDriver/Driver.c: -------------------------------------------------------------------------------- 1 | /*++ 2 | 3 | Module Name: 4 | 5 | driver.c 6 | 7 | Abstract: 8 | 9 | This file contains the driver entry points and callbacks. 10 | 11 | Environment: 12 | 13 | Kernel-mode Driver Framework 14 | 15 | --*/ 16 | 17 | #include "Device.h" 18 | #include "Driver.h" 19 | #include "driver.tmh" 20 | 21 | #ifdef ALLOC_PRAGMA 22 | #pragma alloc_text (INIT, DriverEntry) 23 | #pragma alloc_text (PAGE, SoftU2FEvtDeviceAdd) 24 | #pragma alloc_text (PAGE, SoftU2FEvtDriverContextCleanup) 25 | #endif 26 | 27 | NTSTATUS 28 | DriverEntry( 29 | _In_ PDRIVER_OBJECT DriverObject, 30 | _In_ PUNICODE_STRING RegistryPath 31 | ) 32 | /*++ 33 | 34 | Routine Description: 35 | DriverEntry initializes the driver and is the first routine called by the 36 | system after the driver is loaded. DriverEntry specifies the other entry 37 | points in the function driver, such as EvtDevice and DriverUnload. 38 | 39 | Parameters Description: 40 | 41 | DriverObject - represents the instance of the function driver that is loaded 42 | into memory. DriverEntry must initialize members of DriverObject before it 43 | returns to the caller. DriverObject is allocated by the system before the 44 | driver is loaded, and it is released by the system after the system unloads 45 | the function driver from memory. 46 | 47 | RegistryPath - represents the driver specific path in the Registry. 48 | The function driver can use the path to store driver related data between 49 | reboots. The path does not store hardware instance specific data. 50 | 51 | Return Value: 52 | 53 | STATUS_SUCCESS if successful, 54 | STATUS_UNSUCCESSFUL otherwise. 55 | 56 | --*/ 57 | { 58 | WDF_DRIVER_CONFIG config; 59 | NTSTATUS status; 60 | WDF_OBJECT_ATTRIBUTES attributes; 61 | 62 | // 63 | // Initialize WPP Tracing 64 | // 65 | WPP_INIT_TRACING(DriverObject, RegistryPath); 66 | TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry"); 67 | 68 | ExInitializeDriverRuntime(DrvRtPoolNxOptIn); 69 | 70 | // 71 | // Register a cleanup callback so that we can call WPP_CLEANUP when 72 | // the framework driver object is deleted during driver unload. 73 | // 74 | WDF_OBJECT_ATTRIBUTES_INIT(&attributes); 75 | attributes.EvtCleanupCallback = SoftU2FEvtDriverContextCleanup; 76 | 77 | WDF_DRIVER_CONFIG_INIT(&config, 78 | SoftU2FEvtDeviceAdd 79 | ); 80 | 81 | status = WdfDriverCreate(DriverObject, 82 | RegistryPath, 83 | &attributes, 84 | &config, 85 | WDF_NO_HANDLE 86 | ); 87 | 88 | if (!NT_SUCCESS(status)) { 89 | TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "WdfDriverCreate failed %!STATUS!", status); 90 | WPP_CLEANUP(DriverObject); 91 | return status; 92 | } 93 | 94 | TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit"); 95 | 96 | return status; 97 | } 98 | 99 | NTSTATUS 100 | SoftU2FEvtDeviceAdd( 101 | _In_ WDFDRIVER Driver, 102 | _Inout_ PWDFDEVICE_INIT DeviceInit 103 | ) 104 | /*++ 105 | Routine Description: 106 | 107 | EvtDeviceAdd is called by the framework in response to AddDevice 108 | call from the PnP manager. We create and initialize a device object to 109 | represent a new instance of the device. 110 | 111 | Arguments: 112 | 113 | Driver - Handle to a framework driver object created in DriverEntry 114 | 115 | DeviceInit - Pointer to a framework-allocated WDFDEVICE_INIT structure. 116 | 117 | Return Value: 118 | 119 | NTSTATUS 120 | 121 | --*/ 122 | { 123 | NTSTATUS status; 124 | 125 | UNREFERENCED_PARAMETER(Driver); 126 | 127 | PAGED_CODE(); 128 | 129 | TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry"); 130 | 131 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "SoftU2FCreateDevice called: %s\n", "OK")); 132 | 133 | status = SoftU2FCreateDevice(DeviceInit); 134 | 135 | TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit"); 136 | 137 | return status; 138 | } 139 | 140 | VOID 141 | SoftU2FEvtDriverContextCleanup( 142 | _In_ WDFOBJECT DriverObject 143 | ) 144 | /*++ 145 | Routine Description: 146 | 147 | Free all the resources allocated in DriverEntry. 148 | 149 | Arguments: 150 | 151 | DriverObject - handle to a WDF Driver object. 152 | 153 | Return Value: 154 | 155 | VOID. 156 | 157 | --*/ 158 | { 159 | UNREFERENCED_PARAMETER(DriverObject); 160 | 161 | PAGED_CODE(); 162 | 163 | TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry"); 164 | 165 | // 166 | // Stop WPP Tracing 167 | // 168 | WPP_CLEANUP(WdfDriverWdmGetDriverObject((WDFDRIVER)DriverObject)); 169 | } 170 | -------------------------------------------------------------------------------- /SoftU2FDriver/Driver.h: -------------------------------------------------------------------------------- 1 | /*++ 2 | 3 | Module Name: 4 | 5 | driver.h 6 | 7 | Abstract: 8 | 9 | This file contains the driver definitions. 10 | 11 | Environment: 12 | 13 | Kernel-mode Driver Framework 14 | 15 | --*/ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include "trace.h" 24 | 25 | EXTERN_C_START 26 | 27 | // 28 | // WDFDRIVER Events 29 | // 30 | 31 | DRIVER_INITIALIZE DriverEntry; 32 | EVT_WDF_DRIVER_DEVICE_ADD SoftU2FEvtDeviceAdd; 33 | EVT_WDF_OBJECT_CONTEXT_CLEANUP SoftU2FEvtDriverContextCleanup; 34 | EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL EvtIoDeviceControl; 35 | 36 | 37 | EXTERN_C_END 38 | -------------------------------------------------------------------------------- /SoftU2FDriver/Public.h: -------------------------------------------------------------------------------- 1 | /*++ 2 | 3 | Module Name: 4 | 5 | public.h 6 | 7 | Abstract: 8 | 9 | This module contains the common declarations shared by driver 10 | and user applications. 11 | 12 | Environment: 13 | 14 | user and kernel 15 | 16 | --*/ 17 | 18 | // 19 | // Define an Interface Guid so that apps can find the device and talk to it. 20 | // 21 | #pragma once 22 | 23 | #include 24 | 25 | // {DC3DE777-BC7E-4063-86C9-69422E319AF7} 26 | DEFINE_GUID(GUID_DEVINTERFACE_SOFTU2F_FILTER, 27 | 0xdc3de777, 0xbc7e, 0x4063, 0x86, 0xc9, 0x69, 0x42, 0x2e, 0x31, 0x9a, 0xf7) ; 28 | 29 | // {600C7C9E-3DD0-4521-B931-4398F48A3C5D} 30 | DEFINE_GUID (GUID_BUS_SoftU2F, 31 | 0x600c7c9e, 0x3dd0, 0x4521, 0xb9, 0x31, 0x43, 0x98, 0xf4, 0x8a, 0x3c, 0x5d); 32 | 33 | // {0CA1D3ED-8607-4C22-8AAF-3ECDDA1255DA} 34 | DEFINE_GUID (GUID_DEVCLASS_SoftU2F, 35 | 0x0ca1d3ed, 0x8607, 0x4c22, 0x8a, 0xaf, 0x3e, 0xcd, 0xda, 0x12, 0x55, 0xda); 36 | 37 | #define MILLISECOND 10000 38 | #define RELATIVE_MILLISECOND (-MILLISECOND) 39 | 40 | #define SoftU2F_DEVICE_ID L"SoftU2F\\{600C7C9E-3DD0-4521-B931-4398F48A3C5D}\0" 41 | #define MAX_ID_LEN 128 42 | 43 | #define IOCTL_INDEX 0x800 44 | #define IOCTL_SOFTU2F_FILTER_INIT CTL_CODE(FILE_DEVICE_KEYBOARD, IOCTL_INDEX, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) 45 | #define IOCTL_SOFTU2F_FILTER_WRITE_DATA CTL_CODE(FILE_DEVICE_KEYBOARD, (IOCTL_INDEX + 1), METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) 46 | 47 | #define MAX_BCNT 7609 -------------------------------------------------------------------------------- /SoftU2FDriver/ReadMe.txt: -------------------------------------------------------------------------------- 1 | ======================================================================== 2 | SoftU2F Project Overview 3 | ======================================================================== 4 | 5 | This file contains a summary of what you will find in each of the files that make up your project. 6 | 7 | SoftU2F.vcxproj 8 | This is the main project file for projects generated using an Application Wizard. 9 | It contains information about the version of the product that generated the file, and 10 | information about the platforms, configurations, and project features selected with the 11 | Application Wizard. 12 | 13 | SoftU2F.vcxproj.filters 14 | This is the filters file for VC++ projects generated using an Application Wizard. 15 | It contains information about the association between the files in your project 16 | and the filters. This association is used in the IDE to show grouping of files with 17 | similar extensions under a specific node (for e.g. ".cpp" files are associated with the 18 | "Source Files" filter). 19 | 20 | Public.h 21 | Header file to be shared with applications. 22 | 23 | Driver.c & Driver.h 24 | DriverEntry and WDFDRIVER related functionality and callbacks. 25 | 26 | Device.c & Device.h 27 | WDFDEVICE related functionality and callbacks. 28 | 29 | Queue.c & Queue.h 30 | WDFQUEUE related functionality and callbacks. 31 | 32 | Trace.h 33 | Definitions for WPP tracing. 34 | 35 | ///////////////////////////////////////////////////////////////////////////// 36 | 37 | Learn more about Kernel Mode Driver Framework here: 38 | 39 | http://msdn.microsoft.com/en-us/library/ff544296(v=VS.85).aspx 40 | 41 | ///////////////////////////////////////////////////////////////////////////// 42 | -------------------------------------------------------------------------------- /SoftU2FDriver/SoftU2F.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | Debug 22 | ARM 23 | 24 | 25 | Release 26 | ARM 27 | 28 | 29 | Debug 30 | ARM64 31 | 32 | 33 | Release 34 | ARM64 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | {6480AC5C-E1DE-4855-B9EA-AFA0243EFE88} 57 | {497e31cb-056b-4f31-abb8-447fd55ee5a5} 58 | v4.5 59 | 12.0 60 | Debug 61 | Win32 62 | SoftU2F 63 | SoftU2FDriver 64 | 10.0.22621.0 65 | 66 | 67 | 68 | Windows10 69 | true 70 | WindowsKernelModeDriver10.0 71 | Driver 72 | KMDF 73 | Universal 74 | 75 | 76 | Windows10 77 | false 78 | WindowsKernelModeDriver10.0 79 | Driver 80 | KMDF 81 | Universal 82 | 83 | 84 | Windows10 85 | true 86 | WindowsKernelModeDriver10.0 87 | Driver 88 | KMDF 89 | Desktop 90 | 91 | 92 | Windows10 93 | false 94 | WindowsKernelModeDriver10.0 95 | Driver 96 | KMDF 97 | Universal 98 | 99 | 100 | Windows10 101 | true 102 | WindowsKernelModeDriver10.0 103 | Driver 104 | KMDF 105 | Universal 106 | 107 | 108 | Windows10 109 | false 110 | WindowsKernelModeDriver10.0 111 | Driver 112 | KMDF 113 | Universal 114 | 115 | 116 | Windows10 117 | true 118 | WindowsKernelModeDriver10.0 119 | Driver 120 | KMDF 121 | Universal 122 | 123 | 124 | Windows10 125 | false 126 | WindowsKernelModeDriver10.0 127 | Driver 128 | KMDF 129 | Universal 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | DbgengKernelDebugger 141 | true 142 | true 143 | 144 | 145 | DbgengKernelDebugger 146 | true 147 | true 148 | true 149 | 150 | 151 | DbgengKernelDebugger 152 | true 153 | $(IncludePath) 154 | 155 | 156 | DbgengKernelDebugger 157 | true 158 | 159 | 160 | DbgengKernelDebugger 161 | 162 | 163 | DbgengKernelDebugger 164 | 165 | 166 | DbgengKernelDebugger 167 | 168 | 169 | DbgengKernelDebugger 170 | 171 | 172 | 173 | true 174 | true 175 | trace.h 176 | true 177 | NotUsing 178 | 179 | 180 | 181 | 182 | VhfKm.lib;%(AdditionalDependencies) 183 | 184 | 185 | true 186 | true 187 | 188 | 189 | 190 | 191 | true 192 | true 193 | trace.h 194 | true 195 | 196 | 197 | VhfKm.lib;%(AdditionalDependencies) 198 | 199 | 200 | $(Version) 201 | 202 | 203 | 204 | 205 | true 206 | true 207 | trace.h 208 | true 209 | false 210 | 211 | 212 | $(DDK_LIB_PATH)VhfKm.lib;%(AdditionalDependencies) 213 | 214 | 215 | SHA256 216 | 217 | 218 | 219 | 220 | true 221 | true 222 | trace.h 223 | true 224 | 225 | 226 | $(DDK_LIB_PATH)VhfKm.lib;%(AdditionalDependencies) 227 | 228 | 229 | SHA256 230 | 231 | 232 | Watfaq Technologies Pty Ltd 233 | 234 | 235 | 236 | 237 | true 238 | true 239 | trace.h 240 | true 241 | 242 | 243 | 244 | 245 | true 246 | true 247 | trace.h 248 | true 249 | 250 | 251 | 252 | 253 | true 254 | true 255 | trace.h 256 | true 257 | 258 | 259 | 260 | 261 | true 262 | true 263 | trace.h 264 | true 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | -------------------------------------------------------------------------------- /SoftU2FDriver/SoftU2F.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | {8E41214B-6785-4CFE-B992-037D68949A14} 18 | inf;inv;inx;mof;mc; 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Driver Files 27 | 28 | 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | Header Files 38 | 39 | 40 | Header Files 41 | 42 | 43 | Header Files 44 | 45 | 46 | 47 | 48 | Source Files 49 | 50 | 51 | Source Files 52 | 53 | 54 | Source Files 55 | 56 | 57 | -------------------------------------------------------------------------------- /SoftU2FDriver/SoftU2FDriver.inf: -------------------------------------------------------------------------------- 1 | ; 2 | ; SoftU2F.inf 3 | ; 4 | 5 | [Version] 6 | Signature="$WINDOWS NT$" 7 | Class=HIDClass 8 | ClassGuid={745a17a0-74d3-11d0-b6fe-00a0c90f57da} 9 | Provider=%ManufacturerName% 10 | CatalogFile=SoftU2F.cat 11 | DriverVer=; TODO: set DriverVer in stampinf property pages 12 | PnpLockdown=1 13 | 14 | [DestinationDirs] 15 | DefaultDestDir = 12 16 | SoftU2F_Device_CoInstaller_CopyFiles = 11 17 | 18 | 19 | [SourceDisksNames] 20 | 1 = %DiskName%,,,"" 21 | 22 | [SourceDisksFiles] 23 | SoftU2FDriver.sys = 1,, 24 | WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll=1 ; make sure the number matches with SourceDisksNames 25 | 26 | ;***************************************** 27 | ; Install Section 28 | ;***************************************** 29 | 30 | [Manufacturer] 31 | %ManufacturerName%=Standard,NT$ARCH$ 32 | 33 | [Standard.NT$ARCH$] 34 | %SoftU2F.DeviceDesc%=SoftU2F_Device, Root\SoftU2F 35 | 36 | [SoftU2F_Device.NT] 37 | CopyFiles=Drivers_Dir 38 | 39 | [Drivers_Dir] 40 | SoftU2FDriver.sys 41 | 42 | ;-------------- Service installation 43 | [SoftU2F_Device.NT.Services] 44 | AddService = SoftU2F,%SPSVCINST_ASSOCSERVICE%, SoftU2F_Service_Inst 45 | 46 | ; -------------- SoftU2F driver install sections 47 | [SoftU2F_Service_Inst] 48 | DisplayName = %SoftU2F.SVCDESC% 49 | ServiceType = 1 ; SERVICE_KERNEL_DRIVER 50 | StartType = 3 ; SERVICE_DEMAND_START 51 | ErrorControl = 1 ; SERVICE_ERROR_NORMAL 52 | ServiceBinary = %12%\SoftU2FDriver.sys 53 | 54 | ; 55 | ;--- SoftU2F_Device Coinstaller installation ------ 56 | ; 57 | 58 | [SoftU2F_Device.NT.CoInstallers] 59 | AddReg=SoftU2F_Device_CoInstaller_AddReg 60 | CopyFiles=SoftU2F_Device_CoInstaller_CopyFiles 61 | 62 | [SoftU2F_Device_CoInstaller_AddReg] 63 | HKR,,CoInstallers32,0x00010000, "WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll,WdfCoInstaller" 64 | 65 | [SoftU2F_Device_CoInstaller_CopyFiles] 66 | WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll 67 | 68 | [SoftU2F_Device.NT.Wdf] 69 | KmdfService = SoftU2F, SoftU2F_wdfsect 70 | [SoftU2F_wdfsect] 71 | KmdfLibraryVersion = $KMDFVERSION$ 72 | 73 | [Strings] 74 | SPSVCINST_ASSOCSERVICE= 0x00000002 75 | ManufacturerName="Watfaq Technologies Pty Ltd" 76 | ClassName="U2FHID" 77 | DiskName = "SoftU2F Installation Disk" 78 | SoftU2F.DeviceDesc = "SoftU2F Device" 79 | SoftU2F.SVCDESC = "SoftU2F Driver SVC" 80 | 81 | [SoftU2F_Device.NT.HW] 82 | AddReg = SoftU2F_Device.NT.AddReg 83 | 84 | [SoftU2F_Device.NT.AddReg] 85 | HKR,,"LowerFilters",0x00010000,"vhf" -------------------------------------------------------------------------------- /SoftU2FDriver/Trace.h: -------------------------------------------------------------------------------- 1 | /*++ 2 | 3 | Module Name: 4 | 5 | Trace.h 6 | 7 | Abstract: 8 | 9 | Header file for the debug tracing related function defintions and macros. 10 | 11 | Environment: 12 | 13 | Kernel mode 14 | 15 | --*/ 16 | 17 | // 18 | // Define the tracing flags. 19 | // 20 | // Tracing GUID - 00278b22-3b93-4167-8c9e-0271339d20cb 21 | // 22 | 23 | #define WPP_CONTROL_GUIDS \ 24 | WPP_DEFINE_CONTROL_GUID( \ 25 | SoftU2FTraceGuid, (00278b22,3b93,4167,8c9e,0271339d20cb), \ 26 | \ 27 | WPP_DEFINE_BIT(MYDRIVER_ALL_INFO) \ 28 | WPP_DEFINE_BIT(TRACE_DRIVER) \ 29 | WPP_DEFINE_BIT(TRACE_DEVICE) \ 30 | WPP_DEFINE_BIT(TRACE_QUEUE) \ 31 | ) 32 | 33 | #define WPP_FLAG_LEVEL_LOGGER(flag, level) \ 34 | WPP_LEVEL_LOGGER(flag) 35 | 36 | #define WPP_FLAG_LEVEL_ENABLED(flag, level) \ 37 | (WPP_LEVEL_ENABLED(flag) && \ 38 | WPP_CONTROL(WPP_BIT_ ## flag).Level >= level) 39 | 40 | #define WPP_LEVEL_FLAGS_LOGGER(lvl,flags) \ 41 | WPP_LEVEL_LOGGER(flags) 42 | 43 | #define WPP_LEVEL_FLAGS_ENABLED(lvl, flags) \ 44 | (WPP_LEVEL_ENABLED(flags) && WPP_CONTROL(WPP_BIT_ ## flags).Level >= lvl) 45 | 46 | // 47 | // WPP orders static parameters before dynamic parameters. To support the Trace function 48 | // defined below which sets FLAGS=MYDRIVER_ALL_INFO, a custom macro must be defined to 49 | // reorder the arguments to what the .tpl configuration file expects. 50 | // 51 | #define WPP_RECORDER_FLAGS_LEVEL_ARGS(flags, lvl) WPP_RECORDER_LEVEL_FLAGS_ARGS(lvl, flags) 52 | #define WPP_RECORDER_FLAGS_LEVEL_FILTER(flags, lvl) WPP_RECORDER_LEVEL_FLAGS_FILTER(lvl, flags) 53 | 54 | // 55 | // This comment block is scanned by the trace preprocessor to define our 56 | // Trace function. 57 | // 58 | // begin_wpp config 59 | // FUNC Trace{FLAGS=MYDRIVER_ALL_INFO}(LEVEL, MSG, ...); 60 | // FUNC TraceEvents(LEVEL, FLAGS, MSG, ...); 61 | // end_wpp 62 | // 63 | -------------------------------------------------------------------------------- /SoftU2FDriver/U2F.c: -------------------------------------------------------------------------------- 1 | #include "U2F.h" 2 | 3 | void softu2f_debug_frame(U2FHID_FRAME *frame, BOOLEAN recv) { 4 | UINT8 *data = NULL; 5 | UINT16 dlen = 0; 6 | 7 | if (recv) { 8 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Received frame:\n")); 9 | } 10 | else { 11 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Sending frame:\n")); 12 | } 13 | 14 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "\tCID: 0x%08x\n", frame->cid)); 15 | 16 | switch (FRAME_TYPE(*frame)) { 17 | case TYPE_INIT: 18 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "\tTYPE: INIT\n")); 19 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "\tCMD: 0x%02x\n", frame->init.cmd & ~TYPE_MASK)); 20 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "\tBCNTH: 0x%02x\n", frame->init.bcnth)); 21 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "\tBCNTL: 0x%02x\n", frame->init.bcntl)); 22 | data = frame->init.data; 23 | dlen = HID_RPT_SIZE - 7; 24 | 25 | break; 26 | 27 | case TYPE_CONT: 28 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "\tTYPE: CONT\n")); 29 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "\tSEQ: 0x%02x\n", frame->cont.seq)); 30 | data = frame->cont.data; 31 | dlen = HID_RPT_SIZE - 5; 32 | 33 | break; 34 | } 35 | 36 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "\tDATA:")); 37 | for (int i = 0; i < dlen; i++) { 38 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, " %02x", data[i])); 39 | } 40 | 41 | KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "\n\n")); 42 | } 43 | -------------------------------------------------------------------------------- /SoftU2FDriver/U2F.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #pragma warning(disable:4201) // nameless struct/union 5 | 6 | EXTERN_C_START 7 | 8 | #define TYPE_MASK 0x80 // Frame type mask 9 | 10 | #define TYPE_INIT 0x80 // Initial frame identifier 11 | #define TYPE_CONT 0x00 // Continuation frame identifier 12 | 13 | #define HID_RPT_SIZE 64 // Default size of raw HID report 14 | 15 | #define CID_BROADCAST 0xffffffff // Broadcast channel id 16 | #define U2FHID_IF_VERSION 2 17 | 18 | 19 | #define U2FHID_PING (TYPE_INIT | 0x01) // Echo data through local processor only 20 | #define U2FHID_MSG (TYPE_INIT | 0x03) // Send U2F message frame 21 | #define U2FHID_LOCK (TYPE_INIT | 0x04) // Send lock channel command 22 | #define U2FHID_INIT (TYPE_INIT | 0x06) // Channel initialization 23 | #define U2FHID_WINK (TYPE_INIT | 0x08) // Send device identification wink 24 | #define U2FHID_SYNC (TYPE_INIT | 0x3c) // Protocol resync command 25 | #define U2FHID_ERROR (TYPE_INIT | 0x3f) // Error response 26 | 27 | #define INIT_NONCE_SIZE 8 // Size of channel initialization challenge 28 | #define CAPFLAG_WINK 0x01 // Device supports WINK command 29 | #define CAPFLAG_LOCK 0x02 // Device supports LOCK command 30 | 31 | #define FRAME_TYPE(f) ((f).type & TYPE_MASK) 32 | #define MESSAGE_LEN(f) ((f).init.bcnth*256 + (f).init.bcntl) 33 | #define FRAME_SEQ(f) ((f).cont.seq & ~TYPE_MASK) 34 | 35 | 36 | #pragma region data structures 37 | #pragma pack(push, 1) 38 | 39 | typedef struct _U2FHID_FRAME { 40 | UINT32 cid; 41 | union { 42 | UINT8 type; 43 | struct { 44 | UINT8 cmd; 45 | UINT8 bcnth; 46 | UINT8 bcntl; 47 | UINT8 data[64 - 7]; 48 | } init; 49 | struct { 50 | UINT8 seq; 51 | UINT8 data[64 - 5]; 52 | } cont; 53 | }; 54 | } U2FHID_FRAME, *PU2FHID_FRAME; 55 | 56 | 57 | typedef struct _U2FHID_INIT_REQ { 58 | UINT8 nonce[INIT_NONCE_SIZE]; 59 | }U2FHID_INIT_REQ, *PU2FHID_INIT_REQ; 60 | 61 | typedef struct _U2FHID_INIT_RESP { 62 | UINT8 nonce[INIT_NONCE_SIZE]; // Client application nonce 63 | UINT32 cid; // Channel identifier 64 | UINT8 versionInterface; // Interface version 65 | UINT8 versionMajor; // Major version number 66 | UINT8 versionMinor; // Minor version number 67 | UINT8 versionBuild; // Build version number 68 | UINT8 capFlags; // Capabilities flags 69 | } U2FHID_INIT_RESP, *PU2FHID_INIT_RESP; 70 | 71 | typedef struct _U2F_HID_MESSAGE { 72 | UINT8 cmd; 73 | UINT32 cid; 74 | UINT16 bcnt; 75 | PUCHAR data; 76 | 77 | PUCHAR buf; 78 | UINT16 bufCap; // store the capacity of buf, for memory free 79 | ULONG bufLen; // track the actual buf length 80 | 81 | UINT8 lastSeq; 82 | 83 | struct _U2F_HID_MESSAGE *next; 84 | 85 | LARGE_INTEGER createdAtTicks; 86 | } U2FHID_MESSAGE, *PU2FHID_MESSAGE; 87 | 88 | 89 | typedef struct _IO_CTL_XFER_MESSAGE { 90 | UINT8 cmd; 91 | UINT32 cid; 92 | UINT16 bcnt; 93 | } IO_CTL_XFER_MESSAGE, * PIO_CTL_XFER_MESSAGE; 94 | 95 | #pragma pack(pop) 96 | #pragma endregion 97 | 98 | #define ERR_NONE 0x00 // No error 99 | #define ERR_INVALID_CMD 0x01 // Invalid command 100 | #define ERR_INVALID_PAR 0x02 // Invalid parameter 101 | #define ERR_INVALID_LEN 0x03 // Invalid message length 102 | #define ERR_INVALID_SEQ 0x04 // Invalid message sequencing 103 | #define ERR_MSG_TIMEOUT 0x05 // Message has timed out 104 | #define ERR_CHANNEL_BUSY 0x06 // Channel busy 105 | #define ERR_LOCK_REQUIRED 0x0a // Command requires channel lock 106 | #define ERR_INVALID_CID 0x0b // Message on CID 0 107 | #define ERR_OTHER 0x7f // Other unspecified error 108 | 109 | void softu2f_debug_frame(U2FHID_FRAME* frame, BOOLEAN recv); 110 | 111 | 112 | EXTERN_C_END -------------------------------------------------------------------------------- /SoftU2FDriverPackage/SoftU2FDriverPackage.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | Debug 14 | ARM64 15 | 16 | 17 | Release 18 | ARM64 19 | 20 | 21 | 22 | 23 | {6480ac5c-e1de-4855-b9ea-afa0243efe88} 24 | 25 | 26 | 27 | {B13752F9-1180-4716-9B4B-2D801305F15E} 28 | {4605da2c-74a5-4865-98e1-152ef136825f} 29 | v4.5 30 | 12.0 31 | Debug 32 | x64 33 | SoftU2FDriverPackage 34 | 35 | 36 | 37 | Windows10 38 | true 39 | WindowsKernelModeDriver10.0 40 | Utility 41 | Package 42 | true 43 | 44 | 45 | Windows10 46 | false 47 | WindowsKernelModeDriver10.0 48 | Utility 49 | Package 50 | true 51 | 52 | 53 | Windows10 54 | true 55 | WindowsKernelModeDriver10.0 56 | Utility 57 | Package 58 | true 59 | 60 | 61 | Windows10 62 | false 63 | WindowsKernelModeDriver10.0 64 | Utility 65 | Package 66 | true 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | DbgengKernelDebugger 78 | 79 | 80 | 81 | False 82 | False 83 | True 84 | 85 | 133563 86 | true 87 | 88 | 89 | DbgengKernelDebugger 90 | 91 | 92 | 93 | False 94 | False 95 | True 96 | 97 | 133563 98 | 99 | 100 | DbgengKernelDebugger 101 | 102 | 103 | 104 | False 105 | False 106 | True 107 | 108 | 133563 109 | 110 | 111 | DbgengKernelDebugger 112 | 113 | 114 | 115 | False 116 | False 117 | True 118 | 119 | 133563 120 | 121 | 122 | 123 | SHA256 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /SoftU2FDriverPackage/SoftU2FDriverPackage.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {8E41214B-6785-4CFE-B992-037D68949A14} 6 | inf;inv;inx;mof;mc; 7 | 8 | 9 | -------------------------------------------------------------------------------- /U2FLib/Migrations/20190619171922_InitialCreate.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | using U2FLib.Storage; 8 | 9 | namespace U2FLib.Migrations 10 | { 11 | [DbContext(typeof(AppDbContext))] 12 | [Migration("20190619171922_InitialCreate")] 13 | partial class InitialCreate 14 | { 15 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 16 | { 17 | #pragma warning disable 612, 618 18 | modelBuilder 19 | .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); 20 | 21 | modelBuilder.Entity("U2FLib.Storage.ApplicationData", b => 22 | { 23 | b.Property("Id") 24 | .ValueGeneratedOnAdd(); 25 | 26 | b.Property("Counter"); 27 | 28 | b.HasKey("Id"); 29 | 30 | b.ToTable("ApplicationDatum"); 31 | }); 32 | 33 | modelBuilder.Entity("U2FLib.Storage.KeyPair", b => 34 | { 35 | b.Property("KeyHandle") 36 | .ValueGeneratedOnAdd(); 37 | 38 | b.Property("ApplicationTag"); 39 | 40 | b.Property("Label"); 41 | 42 | b.Property("PrivateKey"); 43 | 44 | b.Property("PublicKey"); 45 | 46 | b.HasKey("KeyHandle"); 47 | 48 | b.ToTable("KeyPairs"); 49 | }); 50 | #pragma warning restore 612, 618 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /U2FLib/Migrations/20190619171922_InitialCreate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | namespace U2FLib.Migrations 5 | { 6 | public partial class InitialCreate : Migration 7 | { 8 | protected override void Up(MigrationBuilder migrationBuilder) 9 | { 10 | migrationBuilder.CreateTable( 11 | name: "ApplicationDatum", 12 | columns: table => new 13 | { 14 | Id = table.Column(nullable: false) 15 | .Annotation("Sqlite:Autoincrement", true), 16 | Counter = table.Column(nullable: false) 17 | }, 18 | constraints: table => 19 | { 20 | table.PrimaryKey("PK_ApplicationDatum", x => x.Id); 21 | }); 22 | 23 | migrationBuilder.CreateTable( 24 | name: "KeyPairs", 25 | columns: table => new 26 | { 27 | KeyHandle = table.Column(nullable: false), 28 | Label = table.Column(nullable: true), 29 | ApplicationTag = table.Column(nullable: true), 30 | PublicKey = table.Column(nullable: true), 31 | PrivateKey = table.Column(nullable: true) 32 | }, 33 | constraints: table => 34 | { 35 | table.PrimaryKey("PK_KeyPairs", x => x.KeyHandle); 36 | }); 37 | } 38 | 39 | protected override void Down(MigrationBuilder migrationBuilder) 40 | { 41 | migrationBuilder.DropTable( 42 | name: "ApplicationDatum"); 43 | 44 | migrationBuilder.DropTable( 45 | name: "KeyPairs"); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /U2FLib/Migrations/AppDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 6 | using U2FLib.Storage; 7 | 8 | namespace U2FLib.Migrations 9 | { 10 | [DbContext(typeof(AppDbContext))] 11 | partial class AppDbContextModelSnapshot : ModelSnapshot 12 | { 13 | protected override void BuildModel(ModelBuilder modelBuilder) 14 | { 15 | #pragma warning disable 612, 618 16 | modelBuilder 17 | .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); 18 | 19 | modelBuilder.Entity("U2FLib.Storage.ApplicationData", b => 20 | { 21 | b.Property("Id") 22 | .ValueGeneratedOnAdd(); 23 | 24 | b.Property("Counter"); 25 | 26 | b.HasKey("Id"); 27 | 28 | b.ToTable("ApplicationDatum"); 29 | }); 30 | 31 | modelBuilder.Entity("U2FLib.Storage.KeyPair", b => 32 | { 33 | b.Property("KeyHandle") 34 | .ValueGeneratedOnAdd(); 35 | 36 | b.Property("ApplicationTag"); 37 | 38 | b.Property("Label"); 39 | 40 | b.Property("PrivateKey"); 41 | 42 | b.Property("PublicKey"); 43 | 44 | b.HasKey("KeyHandle"); 45 | 46 | b.ToTable("KeyPairs"); 47 | }); 48 | #pragma warning restore 612, 618 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /U2FLib/NativeBridge.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/SoftU2F-Win/a00de790ec4536f2b8bf3f2ba421751df0c6bbe8/U2FLib/NativeBridge.dll -------------------------------------------------------------------------------- /U2FLib/Storage/AppDBContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Cryptography; 4 | using System.Text; 5 | using System.Windows; 6 | using Microsoft.EntityFrameworkCore; 7 | using Microsoft.EntityFrameworkCore.Metadata.Internal; 8 | 9 | namespace U2FLib.Storage 10 | { 11 | public class AppDbContext : DbContext 12 | { 13 | public AppDbContext() { } 14 | 15 | public AppDbContext(DbContextOptions options) : base(options) 16 | { 17 | } 18 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 19 | { 20 | var dbPath = Environment.GetEnvironmentVariable("DBPath"); 21 | optionsBuilder.UseSqlite($"Filename = {dbPath}"); 22 | } 23 | 24 | protected override void OnModelCreating(ModelBuilder modelBuilder) 25 | { 26 | modelBuilder.Entity() 27 | .Property(b => b.ApplicationTag) 28 | .HasField("_applicationTag").UsePropertyAccessMode(PropertyAccessMode.Field); 29 | modelBuilder.Entity() 30 | .Property(b => b.PrivateKey) 31 | .HasField("_privateKey").UsePropertyAccessMode(PropertyAccessMode.Field); 32 | modelBuilder.Entity() 33 | .Property(b => b.PublicKey) 34 | .HasField("_publicKey").UsePropertyAccessMode(PropertyAccessMode.Field); 35 | } 36 | 37 | public DbSet KeyPairs { get; set; } 38 | public DbSet ApplicationDatum { get; set; } 39 | } 40 | 41 | public class ApplicationData 42 | { 43 | public uint Id { get; set; } 44 | public UInt32 Counter { get; set; } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /U2FLib/Storage/KeyPair.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Linq; 4 | using System.Security.Cryptography; 5 | using Org.BouncyCastle.Asn1.Nist; 6 | using Org.BouncyCastle.Asn1.X9; 7 | using Org.BouncyCastle.Crypto.Generators; 8 | using Org.BouncyCastle.Crypto.Parameters; 9 | using Org.BouncyCastle.Pkcs; 10 | using Org.BouncyCastle.Security; 11 | 12 | namespace U2FLib.Storage 13 | { 14 | public class KeyPair 15 | { 16 | private static readonly string ECDSA = "ECDSA"; 17 | private static readonly string NISTP256 = "P-256"; 18 | private static readonly SecureRandom secureRandom = new SecureRandom(); 19 | private static readonly X9ECParameters curve = NistNamedCurves.GetByName(NISTP256); 20 | 21 | private static readonly ECDomainParameters domain = 22 | new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H); 23 | 24 | private byte[] _applicationTag; 25 | private byte[] _privateKey; 26 | private byte[] _publicKey; 27 | 28 | private bool dataProtected => !Environment.GetCommandLineArgs().Contains("--db-unprotected"); 29 | 30 | public KeyPair() 31 | { 32 | } 33 | 34 | // Generate a KeyPair with given label 35 | public KeyPair(string label) 36 | { 37 | Label = label; 38 | 39 | var g = new ECKeyPairGenerator(ECDSA); 40 | var gParams = new ECKeyGenerationParameters(domain, secureRandom); 41 | g.Init(gParams); 42 | var keyPair = g.GenerateKeyPair(); 43 | 44 | PrivateKey = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private).GetDerEncoded(); 45 | 46 | var ecPublicKey = (ECPublicKeyParameters)keyPair.Public; 47 | PublicKey = ecPublicKey.Q.GetEncoded(); 48 | 49 | KeyHandle = Convert.ToBase64String(sha512(PublicKey)); 50 | 51 | } 52 | 53 | public string Label { get; set; } 54 | 55 | [Key] public string KeyHandle { get; set; } 56 | 57 | public byte[] ApplicationTag 58 | { 59 | get => UnProtect(_applicationTag); 60 | set => _applicationTag = Protect(value); 61 | } 62 | 63 | public byte[] PublicKey 64 | { 65 | get => UnProtect(_publicKey); 66 | set => _publicKey = Protect(value); 67 | } 68 | 69 | public byte[] PrivateKey 70 | { 71 | get => UnProtect(_privateKey); 72 | set => _privateKey = Protect(value); 73 | } 74 | 75 | private byte[] Protect(byte[] data) 76 | { 77 | if (!dataProtected) return data; 78 | 79 | try 80 | { 81 | return ProtectedData.Protect(data, null, DataProtectionScope.CurrentUser); 82 | } 83 | catch 84 | { 85 | return null; 86 | } 87 | } 88 | 89 | private byte[] UnProtect(byte[] data) 90 | { 91 | if (!dataProtected) return data; 92 | 93 | try 94 | { 95 | return ProtectedData.Unprotect(data, null, DataProtectionScope.CurrentUser); 96 | } 97 | catch 98 | { 99 | return null; 100 | } 101 | } 102 | 103 | private static byte[] sha512(byte[] data) 104 | { 105 | using (var hasher = new SHA512Managed()) 106 | { 107 | return hasher.ComputeHash(data); 108 | } 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /U2FLib/U2FBackgroundTask.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Threading; 8 | using APDU; 9 | 10 | #endregion 11 | 12 | #pragma warning disable CS0414 13 | 14 | namespace U2FLib 15 | { 16 | #region Constants 17 | 18 | using LPSECURITY_ATTRIBUTES = IntPtr; 19 | using LPOVERLAPPED = IntPtr; 20 | using LPVOID = IntPtr; 21 | using HANDLE = IntPtr; 22 | using LARGE_INTEGER = Int64; 23 | using DWORD = UInt32; 24 | using LPCTSTR = String; 25 | 26 | #endregion 27 | 28 | public interface IU2FBackgroundTask 29 | { 30 | void StartIoLoop(CancellationToken token); 31 | bool OpenDevice(); 32 | } 33 | 34 | public sealed partial class BackgroundTask : IU2FBackgroundTask 35 | { 36 | 37 | private static IntPtr _device; 38 | 39 | public BackgroundTask() 40 | { 41 | } 42 | 43 | public void StartIoLoop(CancellationToken token) 44 | { 45 | 46 | IRawConvertible response = null; 47 | IO_CTL_XFER_MESSAGE replyTo = default; 48 | while (!token.IsCancellationRequested) 49 | { 50 | // enter inverted call 51 | replyTo = SendInitRequest(out var nTransferred, out var data, response, replyTo); 52 | response = HandleRequest(data, replyTo); 53 | } 54 | } 55 | 56 | private IRawConvertible HandleRequest(byte[] data, IO_CTL_XFER_MESSAGE request) 57 | { 58 | try 59 | { 60 | var ins = Command.CommandType(data); 61 | 62 | IRawConvertible response; 63 | switch (ins) 64 | { 65 | case CommandCode.Register: 66 | response = HandleRegisterRequest(data, request); 67 | break; 68 | 69 | case CommandCode.Version: 70 | response = HandleVersionRequest(data, request); 71 | break; 72 | 73 | case CommandCode.Authenticate: 74 | response = HandleAuthenticationRequest(data, request); 75 | break; 76 | 77 | default: 78 | response = CreateError(ProtocolErrorCode.InsNotSupported); 79 | break; 80 | } 81 | 82 | return response; 83 | } 84 | catch (ProtocolError e) 85 | { 86 | return CreateError(e.ErrorCode); 87 | } 88 | catch 89 | { 90 | return CreateError(ProtocolErrorCode.OtherError); 91 | } 92 | 93 | } 94 | 95 | public bool OpenDevice() 96 | { 97 | var ptr = GetInterfaceDevicePath(); 98 | if (ptr == IntPtr.Zero) return false; 99 | var devicePath = Marshal.PtrToStringUni(ptr); 100 | _device = CreateFile(devicePath, FILE_READ_DATA | FILE_WRITE_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, 101 | IntPtr.Zero, 102 | OPEN_EXISTING, 0, 0); 103 | return _device != IntPtr.Zero; 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /U2FLib/U2FHIDDeviceCommunication.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using APDU; 9 | 10 | #pragma warning disable CS0414 11 | 12 | namespace U2FLib 13 | { 14 | partial class BackgroundTask 15 | { 16 | private static readonly uint FILE_DEVICE_KEYBOARD = 0x0000000b; 17 | private static readonly uint METHOD_BUFFERED = 0; 18 | private static uint METHOD_IN_DIRECT = 1; 19 | private static uint METHOD_OUT_DIRECT = 2; 20 | private static uint METHOD_NEITHER = 3; 21 | 22 | private static uint FILE_ANY_ACCESS = 0; 23 | private static readonly uint FILE_READ_DATA = 1; 24 | private static readonly uint FILE_WRITE_DATA = 2; 25 | 26 | private static uint GENERIC_READ = 0x80000000; 27 | private static uint GENERIC_WRITE = 0x40000000; 28 | 29 | private static readonly int FILE_SHARE_READ = 0x00000001; 30 | private static readonly int FILE_SHARE_WRITE = 0x00000002; 31 | 32 | private static readonly int OPEN_EXISTING = 3; 33 | 34 | private static uint FILE_ATTRIBUTE_NORMAL = 0x80; 35 | 36 | private static readonly int MAX_BCNT = 7609; 37 | 38 | private static readonly uint IOCTL_INDEX = 0x800; 39 | private static readonly uint IOCTL_SOFTU2F_FILTER_INIT = CTL_CODE(FILE_DEVICE_KEYBOARD, IOCTL_INDEX, 40 | METHOD_BUFFERED, 41 | FILE_READ_DATA | FILE_WRITE_DATA); 42 | 43 | private readonly int 44 | IO_CTL_XFER_MESSAGE_LEN = 45 | Marshal.SizeOf(); // the length HID header info in the IO_CTL_XFER_MESSAGE; 46 | 47 | private static uint CTL_CODE(uint deviceType, uint function, uint method, uint access) 48 | { 49 | return (deviceType << 16) | (access << 14) | (function << 2) | method; 50 | } 51 | 52 | private IO_CTL_XFER_MESSAGE SendInitRequest(out uint nTransferred, out byte[] data, 53 | IRawConvertible response = null, IO_CTL_XFER_MESSAGE replyTo = default) 54 | { 55 | nTransferred = 0; 56 | data = default; 57 | var outputBuffer = new byte[MAX_BCNT + IO_CTL_XFER_MESSAGE_LEN]; 58 | var outputBufferHandle = GCHandle.Alloc(outputBuffer, GCHandleType.Pinned); 59 | var outputBufferPtr = outputBufferHandle.AddrOfPinnedObject(); 60 | var outputBufferLen = (uint)outputBuffer.Length; 61 | GCHandle inputBufferHandle = default; 62 | 63 | var inputBufferPtr = IntPtr.Zero; 64 | uint inputBufferLen = 0; 65 | 66 | if (response != null) 67 | { 68 | var reply = new IO_CTL_XFER_MESSAGE(); 69 | reply.cid = replyTo.cid; 70 | reply.cmd = replyTo.cmd; 71 | reply.bcnt = (short)response.Raw.Length; 72 | 73 | var messageHeader = StructToBytes(reply); 74 | 75 | var inputBuffer = messageHeader.Concat(response.Raw).ToArray(); 76 | inputBufferHandle = GCHandle.Alloc(inputBuffer, GCHandleType.Pinned); 77 | inputBufferPtr = inputBufferHandle.AddrOfPinnedObject(); 78 | inputBufferLen = (uint)inputBuffer.Length; 79 | } 80 | 81 | // block on inverted call 82 | var result = DeviceIoControl( 83 | _device, 84 | IOCTL_SOFTU2F_FILTER_INIT, 85 | inputBufferPtr, inputBufferLen, 86 | outputBufferPtr, outputBufferLen, 87 | ref nTransferred, IntPtr.Zero); 88 | 89 | if (result == 0) return default; 90 | 91 | var xferMessage = 92 | ByteArrayToStructure(outputBuffer.Take(IO_CTL_XFER_MESSAGE_LEN).ToArray()); 93 | data = outputBuffer.Skip(IO_CTL_XFER_MESSAGE_LEN).Take((int)(nTransferred - IO_CTL_XFER_MESSAGE_LEN)).ToArray(); 94 | 95 | outputBufferHandle.Free(); 96 | if (inputBufferHandle != default) inputBufferHandle.Free(); 97 | 98 | return xferMessage; 99 | } 100 | 101 | private const string BridgeDllPath = "NativeBridge.dll"; 102 | [DllImport(BridgeDllPath, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)] 103 | public static extern IntPtr GetInterfaceDevicePath(); 104 | 105 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 106 | internal static extern IntPtr CreateFile( 107 | string lpFileName, 108 | uint dwDesiredAccess, 109 | int dwShareMode, 110 | IntPtr lpSecurityAttributes, 111 | int dwCreationDisposition, 112 | int dwFlagsAndAttributes, 113 | int hTemplateFile); 114 | 115 | [DllImport("kernel32.dll", SetLastError = true)] 116 | private static extern uint DeviceIoControl( 117 | IntPtr hDevice, 118 | uint dwIoControlCode, 119 | IntPtr lpInBuffer, 120 | uint nInBufferSize, 121 | IntPtr lpOutBuffer, 122 | uint nOutBufferSize, 123 | ref uint lpBytesReturned, 124 | IntPtr lpOverlapped 125 | ); 126 | 127 | private static T ByteArrayToStructure(byte[] bytes) where T : struct 128 | 129 | { 130 | var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); 131 | try 132 | { 133 | return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); 134 | } 135 | finally 136 | { 137 | handle.Free(); 138 | } 139 | } 140 | 141 | private static byte[] StructToBytes(T s) where T : struct 142 | { 143 | var size = Marshal.SizeOf(s); 144 | var rv = new byte[size]; 145 | 146 | var ptr = Marshal.AllocHGlobal(size); 147 | Marshal.StructureToPtr(s, ptr, true); 148 | Marshal.Copy(ptr, rv, 0, size); 149 | Marshal.FreeHGlobal(ptr); 150 | return rv; 151 | } 152 | 153 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 154 | public struct IO_CTL_XFER_MESSAGE 155 | { 156 | public byte cmd; 157 | public int cid; 158 | public short bcnt; 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /U2FLib/U2FHIDHandlers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | using U2FLib.Storage; 7 | using APDU; 8 | 9 | namespace U2FLib 10 | { 11 | partial class BackgroundTask 12 | { 13 | private IRawConvertible HandleVersionRequest(byte[] rawData, IO_CTL_XFER_MESSAGE request) 14 | { 15 | 16 | var _ = new VersionRequest(rawData); // validate request data; 17 | return new VersionResponse("U2F_V2"); 18 | } 19 | 20 | private IRawConvertible HandleRegisterRequest(byte[] rawData, IO_CTL_XFER_MESSAGE request) 21 | { 22 | var req = new RegisterRequest(rawData); 23 | var facet = KnownFacets.GetKnownFacet(req.ApplicationParameter); 24 | var ss = Encoding.UTF8.GetString(req.ApplicationParameter); 25 | if (facet == "bogus") 26 | { 27 | return CreateError(ProtocolErrorCode.OtherError); 28 | } 29 | 30 | if (!UserPresence.Present) 31 | { 32 | UserPresence.AskAsync(UserPresence.PresenceType.Registration, facet); 33 | return CreateError(ProtocolErrorCode.ConditionNoSatisfied); 34 | } 35 | 36 | UserPresence.Take(); 37 | U2FRegistration reg; 38 | try 39 | { 40 | reg = new U2FRegistration(req.ApplicationParameter); 41 | } 42 | catch 43 | { 44 | return CreateError(ProtocolErrorCode.OtherError); 45 | } 46 | 47 | var publicKey = reg.KeyPair.PublicKey; 48 | if (publicKey == null) return CreateError(ProtocolErrorCode.OtherError); 49 | 50 | var payloadSize = 1 + req.ApplicationParameter.Length + req.ChallengeParameter.Length + 51 | reg.KeyHandle.Length + publicKey.Length; 52 | 53 | var sigPayload = new List(payloadSize); 54 | sigPayload.Add(0); 55 | sigPayload.AddRange(req.ApplicationParameter); 56 | sigPayload.AddRange(req.ChallengeParameter); 57 | sigPayload.AddRange(reg.KeyHandle); 58 | sigPayload.AddRange(publicKey); 59 | 60 | var sig = Signature.SignData(sigPayload.ToArray()); 61 | var resp = new RegisterResponse(publicKey, keyHandle: reg.KeyHandle, 62 | certificate: Signature.GetCertificatePublicKeyInDer(), signature: sig); 63 | 64 | return resp; 65 | } 66 | 67 | private IRawConvertible HandleAuthenticationRequest(byte[] rawData, BackgroundTask.IO_CTL_XFER_MESSAGE request) 68 | { 69 | var req = new AuthenticationRequest(rawData); 70 | 71 | var reg = U2FRegistration.Find(keyHandle: req.KeyHandle, applicationParameter: req.ApplicationParameter); 72 | if (reg == null) return CreateError(ProtocolErrorCode.WrongData); 73 | 74 | if (req.Control == Control.CheckOnly) return CreateError(ProtocolErrorCode.ConditionNoSatisfied); 75 | 76 | var facet = KnownFacets.GetKnownFacet(req.ApplicationParameter); 77 | 78 | if (!UserPresence.Present) 79 | { 80 | UserPresence.AskAsync(UserPresence.PresenceType.Authentication, facet); 81 | return CreateError(ProtocolErrorCode.ConditionNoSatisfied); 82 | } 83 | 84 | UserPresence.Take(); 85 | ApplicationData appData; 86 | using (var db = new AppDbContext()) 87 | { 88 | appData = db.ApplicationDatum.First(); 89 | if (appData == null) return CreateError(ProtocolErrorCode.OtherError); 90 | appData.Counter += 1; 91 | db.SaveChanges(); 92 | } 93 | 94 | var payloadSize = req.ApplicationParameter.Length + 1 + Marshal.SizeOf() + 95 | req.ApplicationParameter.Length; 96 | var sigPayload = new List(capacity: payloadSize); 97 | sigPayload.AddRange(req.ApplicationParameter); 98 | sigPayload.Add(0x01); // user present 99 | 100 | var counterBytes = BitConverter.GetBytes(appData.Counter); 101 | if (BitConverter.IsLittleEndian) Array.Reverse(counterBytes); 102 | sigPayload.AddRange(counterBytes); 103 | 104 | sigPayload.AddRange(req.ChallengeParameter); 105 | 106 | try 107 | { 108 | var sig = Signature.SignData(sigPayload.ToArray(), reg.KeyPair.PrivateKey); 109 | return new AuthenticationResponse(userPresence: 0x01, counter: appData.Counter, sig); 110 | } 111 | catch 112 | { 113 | return CreateError(ProtocolErrorCode.OtherError); 114 | } 115 | 116 | } 117 | 118 | private IRawConvertible CreateError(ProtocolErrorCode code) 119 | { 120 | return new ErrorResponse(code); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /U2FLib/U2FLib.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0-windows 5 | 6 | 7 | 8 | None 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /U2FLib/U2FRegistration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using U2FLib.Storage; 6 | 7 | namespace U2FLib 8 | { 9 | public class U2FRegistration 10 | { 11 | private static readonly string KP_Lable = "SoftU2F Security Key"; 12 | 13 | public KeyPair KeyPair; 14 | public byte[] ApplicationParameter; 15 | public byte[] KeyHandle => Convert.FromBase64String(KeyPair.KeyHandle); 16 | 17 | public U2FRegistration(byte[] applicationParameter) 18 | { 19 | using (var context = new AppDbContext()) 20 | { 21 | var kp = new KeyPair(KP_Lable) { ApplicationTag = applicationParameter }; 22 | context.KeyPairs.Add(kp); 23 | context.SaveChanges(); 24 | KeyPair = kp; 25 | } 26 | } 27 | 28 | public U2FRegistration(KeyPair keyPair, byte[] applicationParameter) 29 | { 30 | KeyPair = keyPair; 31 | ApplicationParameter = applicationParameter; 32 | } 33 | 34 | public static U2FRegistration Find(byte[] keyHandle, byte[] applicationParameter) 35 | { 36 | var sKeyHandle = Convert.ToBase64String(keyHandle); 37 | using (var context = new AppDbContext()) 38 | { 39 | var kp = context.KeyPairs.SingleOrDefault(p => p.KeyHandle == sKeyHandle); 40 | if (kp == null) return null; 41 | 42 | var ap = kp.ApplicationTag; 43 | return !applicationParameter.SequenceEqual(ap) ? null : new U2FRegistration(kp, applicationParameter); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /U2FLib/UserPresence.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using U2FLib; 8 | using Org.BouncyCastle.Asn1.X509; 9 | using Timer = System.Timers.Timer; 10 | 11 | namespace U2FLib 12 | { 13 | public interface INotifySender 14 | { 15 | void Send(string title, string message, Action userClicked); 16 | } 17 | 18 | public class UserPresence 19 | { 20 | private static bool _present; 21 | private static readonly object _presentLock = new object(); 22 | 23 | public enum PresenceType: byte 24 | { 25 | Registration = 1, 26 | Authentication = 2 27 | } 28 | 29 | public static bool Present 30 | { 31 | get 32 | { 33 | lock (_presentLock) 34 | { 35 | return _present; 36 | } 37 | } 38 | private set 39 | { 40 | lock (_presentLock) 41 | { 42 | _present = value; 43 | } 44 | } 45 | } 46 | 47 | private static readonly Timer PresenceTimeout; 48 | 49 | public static INotifySender Sender; 50 | 51 | static UserPresence() 52 | { 53 | PresenceTimeout = new Timer(TimeSpan.FromSeconds(10).TotalMilliseconds) 54 | { 55 | AutoReset = false // only fire once 56 | }; 57 | PresenceTimeout.Elapsed += (sender, args) => Present = false; 58 | PresenceTimeout.Enabled = false; 59 | } 60 | 61 | public static void AskAsync(PresenceType type, string facet) 62 | { 63 | var title = ""; 64 | facet = string.IsNullOrEmpty(facet) ? "Unknown Facet" : facet; 65 | var message = ""; 66 | switch (type) 67 | { 68 | case PresenceType.Authentication: 69 | title = "Authentication Request"; 70 | message = $"Authentication with {facet}"; 71 | break; 72 | case PresenceType.Registration: 73 | title = "Registration Request"; 74 | message = $"Register with {facet}"; 75 | break; 76 | } 77 | 78 | message += "\nClick to Allow"; 79 | Sender?.Send(title, message, delegate(bool b) 80 | { 81 | if (b) 82 | { 83 | Set(); 84 | } 85 | }); 86 | } 87 | 88 | // use pressed the button 89 | public static void Set() 90 | { 91 | if (Present) 92 | { 93 | PresenceTimeout.Stop(); 94 | } 95 | Present = true; 96 | PresenceTimeout.Start(); 97 | } 98 | 99 | // the presence is used 100 | public static void Take() 101 | { 102 | Present = false; 103 | PresenceTimeout.Stop(); 104 | } 105 | } 106 | } 107 | --------------------------------------------------------------------------------