├── .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 | [](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 | 
28 |
29 | ### Authentication
30 |
31 | 
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 |
--------------------------------------------------------------------------------