├── .gitattributes ├── .gitignore ├── APDU.cs ├── Driver ├── BixVReader-Cert.cer └── BixVReaderInstaller.msi ├── DriverCom ├── NullHandler.cs ├── PipeCom.cs └── SocketCom.cs ├── Interfaces.cs ├── PIVCardHandler.cs ├── PIVert.csproj ├── PIVert.sln ├── Program.cs ├── Properties └── launchSettings.json ├── README.md ├── RemoteReaderSettings.cs ├── Util.cs └── Yubikey ├── Cryptography ├── CryptographyProviders.cs └── RandomNumberGeneratorExt.cs ├── Iso7816 ├── AnswerToReset.cs ├── ApduEncoding.cs ├── ApduException.cs ├── CommandApdu.cs ├── ResponseApdu.cs ├── SW1Constants.cs └── SWConstants.cs ├── Logging ├── Log.cs └── Logger.cs ├── Objects ├── CardCapabilityContainer.cs ├── CardholderUniqueId.cs └── PivDataObject.cs ├── Resources ├── Core │ ├── ExceptionMessages.Designer.cs │ └── ExceptionMessages.resx └── Yubikey │ ├── ExceptionMessages.Designer.cs │ └── ExceptionMessages.resx └── Tlv ├── TlvEncoder.cs ├── TlvException.cs ├── TlvNestedTlv.cs ├── TlvReader.cs ├── TlvSubElement.cs └── TlvWriter.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /APDU.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ISO7816 { 4 | public class Apdu 5 | { 6 | public Byte INS; 7 | public Byte CLA; 8 | public Byte P1; 9 | public Byte P2; 10 | public byte[] Data; 11 | public Byte LE; 12 | public bool UseLE; 13 | 14 | public byte[] SMRandom; 15 | public byte[] SMEncKeyOut; 16 | public byte[] SMSigKeyOut; 17 | 18 | public Apdu() 19 | { 20 | } 21 | 22 | public Apdu(byte[] data) 23 | { 24 | CLA = data[0]; 25 | INS = data[1]; 26 | P1 = data[2]; 27 | P2 = data[3]; 28 | if (data.Length == 4) { 29 | this.Data = null; 30 | UseLE = false; 31 | return; 32 | } 33 | 34 | if (data.Length == 5) 35 | { 36 | this.Data = null; 37 | UseLE = true; 38 | LE = data[4]; 39 | return; 40 | } 41 | 42 | if (data.Length == 5 + data[4]) 43 | { 44 | this.Data = new byte[data[4]]; 45 | UseLE = false; 46 | Array.Copy(data, 5, Data, 0, data[4]); 47 | return; 48 | } 49 | 50 | if (data.Length == 6 + data[4]) 51 | { 52 | this.Data = new byte[data[4]]; 53 | UseLE = true; 54 | Array.Copy(data, 5, Data, 0, data[4]); 55 | LE = data[data.Length - 1]; 56 | return; 57 | } 58 | } 59 | 60 | public Apdu(Byte _CLA, Byte _INS, Byte _P1, Byte _P2, byte[] _Data, Byte _LE) 61 | { 62 | INS = _INS; 63 | CLA = _CLA; 64 | P1 = _P1; 65 | P2 = _P2; 66 | Data = _Data; 67 | LE = _LE; 68 | UseLE = true; 69 | } 70 | 71 | public Apdu(Byte _CLA, Byte _INS, Byte _P1, Byte _P2, byte[] _Data) 72 | { 73 | INS = _INS; 74 | CLA = _CLA; 75 | P1 = _P1; 76 | P2 = _P2; 77 | Data = _Data; 78 | LE = 0; 79 | UseLE = false; 80 | } 81 | 82 | public Apdu(Byte _CLA, Byte _INS, Byte _P1, Byte _P2, Byte _LE) 83 | { 84 | INS = _INS; 85 | CLA = _CLA; 86 | P1 = _P1; 87 | P2 = _P2; 88 | Data = null; 89 | LE = _LE; 90 | UseLE = true; 91 | } 92 | public Apdu(Byte _CLA, Byte _INS, Byte _P1, Byte _P2) 93 | { 94 | INS = _INS; 95 | CLA = _CLA; 96 | P1 = _P1; 97 | P2 = _P2; 98 | LE = 0; 99 | Data = null; 100 | UseLE = false; 101 | } 102 | 103 | public static bool IsRespOK(byte[] resp) 104 | { 105 | if (resp == null) 106 | return false; 107 | if (resp.Length < 2) 108 | return false; 109 | if (resp[resp.Length - 2] != 0x90 || 110 | resp[resp.Length - 1] != 0x00) 111 | return false; 112 | return true; 113 | } 114 | 115 | public bool IsSM { get { return (CLA & 0x0c) != 0; } } 116 | 117 | public override string ToString() 118 | { 119 | String txt; 120 | txt = String.Format("{0:X2} {1:X2} {2:X2} {3:X2} ", CLA, INS, P1, P2); 121 | if (Data != null) 122 | { 123 | txt += Data.Length.ToString("X2") + " "; 124 | foreach (byte b in Data) 125 | { 126 | txt += b.ToString("X2"); 127 | } 128 | txt += " "; 129 | } 130 | if (UseLE) 131 | txt += LE.ToString("X2"); 132 | return txt; 133 | } 134 | 135 | public byte[] GetBytes() 136 | { 137 | int iAPDUSize = 4; 138 | if (Data != null) 139 | iAPDUSize += Data.Length + 1; 140 | if (UseLE) 141 | iAPDUSize++; 142 | 143 | byte[] pbtAPDU = new byte[iAPDUSize]; 144 | pbtAPDU[0] = CLA; 145 | pbtAPDU[1] = INS; 146 | pbtAPDU[2] = P1; 147 | pbtAPDU[3] = P2; 148 | if (Data != null && UseLE) 149 | { 150 | pbtAPDU[4] = (byte)Data.Length; 151 | Data.CopyTo(pbtAPDU, 5); 152 | pbtAPDU[5 + Data.Length] = LE; 153 | } 154 | else if (Data != null && !UseLE) 155 | { 156 | pbtAPDU[4] = (byte)Data.Length; 157 | Data.CopyTo(pbtAPDU, 5); 158 | } 159 | else if (Data == null && UseLE) 160 | { 161 | pbtAPDU[4] = LE; 162 | } 163 | 164 | return pbtAPDU; 165 | } 166 | 167 | public static Apdu Select(ushort id) 168 | { 169 | return new Apdu(0x00, 0xA4, 0x00, 0x00, new byte[] { (byte)(id >> 8), (byte)(id & 0xff) }, 0xFF); 170 | } 171 | 172 | public static Apdu Select(byte[] id) 173 | { 174 | return new Apdu(0x00, 0xA4, 0x00, 0x00, id, 0xFF); 175 | } 176 | 177 | public static Apdu SelectMF() 178 | { 179 | return new Apdu(0x00, 0xA4, 0x00, 0x00); 180 | } 181 | 182 | public static Apdu SelectByAID(byte[] AID) 183 | { 184 | return new Apdu(0x00, 0xA4, 0x04, 0x00, AID); 185 | } 186 | 187 | public static Apdu SelectByAbsolutePath(byte[] path) 188 | { 189 | return new Apdu(0x00, 0xA4, 0x08, 0x00, path); 190 | } 191 | 192 | public static Apdu SelectByRelativePath(byte[] path) 193 | { 194 | return new Apdu(0x00, 0xA4, 0x09, 0x00, path); 195 | } 196 | 197 | public static Apdu Parent() 198 | { 199 | return new Apdu(0x00, 0xA4, 0x03, 0x00); 200 | } 201 | 202 | public static Apdu ReadBinary(int start, byte size) 203 | { 204 | return new Apdu(0x00, 0xB0, (byte)(start >> 8), (byte)(start & 0xff), size); 205 | } 206 | 207 | public static Apdu UpdateBinary(int start, byte[] data) 208 | { 209 | return new Apdu(0x00, 0xD6, (byte)(start >> 8), (byte)(start & 0xff), data); 210 | } 211 | 212 | public static Apdu AppendRecord(byte[] data) 213 | { 214 | return new Apdu(0x00, 0xE2, 0x00, 0x00, data); 215 | } 216 | 217 | public static Apdu ReadRecordCurrent() 218 | { 219 | return new Apdu(0x00, 0xB2, 0x00, 0x04, 0x00); 220 | } 221 | 222 | public static Apdu ReadRecordAbsolute(byte index) 223 | { 224 | return new Apdu(0x00, 0xB2, index, 0x04, 0x00); 225 | } 226 | public static Apdu ReadRecordFirst() 227 | { 228 | return new Apdu(0x00, 0xB2, 0x00, 0x00, 0x00); 229 | } 230 | public static Apdu ReadRecordLast() 231 | { 232 | return new Apdu(0x00, 0xB2, 0x00, 0x01, 0x00); 233 | } 234 | public static Apdu ReadRecordNext() 235 | { 236 | return new Apdu(0x00, 0xB2, 0x00, 0x02, 0x00); 237 | } 238 | public static Apdu ReadRecordPrevious() 239 | { 240 | return new Apdu(0x00, 0xB2, 0x00, 0x03, 0x00); 241 | } 242 | 243 | public static Apdu UpdateRecordCurrent(byte[] data) 244 | { 245 | return new Apdu(0x00, 0xDC, 0x00, 0x04, data); 246 | } 247 | public static Apdu UpdateRecordAbsolute(byte index, byte[] data) 248 | { 249 | return new Apdu(0x00, 0xDC, index, 0x04, data); 250 | } 251 | public static Apdu UpdateRecordFirst(byte[] data) 252 | { 253 | return new Apdu(0x00, 0xDC, 0x00, 0x00, data); 254 | } 255 | public static Apdu UpdateRecordLast(byte[] data) 256 | { 257 | return new Apdu(0x00, 0xDC, 0x00, 0x01, data); 258 | } 259 | public static Apdu UpdateRecordNext(byte[] data) 260 | { 261 | return new Apdu(0x00, 0xDC, 0x00, 0x02, data); 262 | } 263 | public static Apdu UpdateRecordPrev(byte[] data) 264 | { 265 | return new Apdu(0x00, 0xDC, 0x00, 0x03, data); 266 | } 267 | public static Apdu Verify(bool BackTracking, byte pinID, byte[] pin) 268 | { 269 | return new Apdu(0x00, 0x20, 0x00, (byte)(pinID & 0x7F | (BackTracking ? 0x80 : 0)), pin); 270 | } 271 | public static Apdu ExternalAuthenticate(bool BackTracking, byte keyID, byte[] data) 272 | { 273 | return new Apdu(0x00, 0x82, 0x00, (byte)(keyID & 0x7F | (BackTracking ? 0x80 : 0)), data); 274 | } 275 | public static Apdu GetChallenge(byte size) 276 | { 277 | return new Apdu(0x00, 0x84, 0x00, 0x00, size); 278 | } 279 | public static Apdu GiveRandom(byte[] random) 280 | { 281 | return new Apdu(0x80, 0x86, 0x00, 0x00, random); 282 | } 283 | public static Apdu InternalAuthenticate(bool BackTracking, byte keyID, byte[] data) 284 | { 285 | return new Apdu(0x00, 0x88, 0x00, (byte)(keyID & 0x7F | (BackTracking ? 0x80 : 0)), data, (byte)data.Length); 286 | } 287 | public static Apdu ResetRetryCountr(bool BackTracking, byte pinID) 288 | { 289 | return Apdu.ResetRetryCounter(BackTracking, pinID, null, null); 290 | } 291 | public static Apdu ResetRetryCounter(bool BackTracking, byte pinID, byte[] PUK) 292 | { 293 | return Apdu.ResetRetryCounter(BackTracking, pinID, PUK, null); 294 | } 295 | public static Apdu ResetRetryCounter(bool BackTracking, byte pinID, byte[] PUK, byte[] newPin) 296 | { 297 | byte mode; 298 | if (PUK == null && newPin == null) 299 | mode = 3; 300 | else if (newPin == null) 301 | mode = 1; 302 | else 303 | mode = 0; 304 | byte[] pPuk = (PUK != null) ? PUK : new byte[0]; 305 | byte[] pPin = (newPin != null) ? newPin : new byte[0]; 306 | byte[] data = new byte[pPuk.Length + pPin.Length]; 307 | pPuk.CopyTo(data, 0); 308 | pPin.CopyTo(data, pPuk.Length); 309 | return new Apdu(0x00, 0x2C, mode, (byte)(pinID & 0x7F | (BackTracking ? 0x80 : 0)), data); 310 | 311 | } 312 | public static Apdu ChangeReferenceData(bool BackTracking, byte pinID, byte[] oldPin, byte[] newPin) 313 | { 314 | byte Explicit = 0; 315 | byte[] data; 316 | if (oldPin == null) 317 | { 318 | data = newPin; 319 | Explicit = 1; 320 | } 321 | else 322 | { 323 | data = new byte[oldPin.Length + newPin.Length]; 324 | oldPin.CopyTo(data, 0); 325 | newPin.CopyTo(data, oldPin.Length); 326 | } 327 | return new Apdu(0x00, 0x24, Explicit, (byte)(pinID & 0x7F | (BackTracking ? 0x80 : 0)), data); 328 | } 329 | public static Apdu ChangeKeyData(bool BackTracking, byte pinID, byte[] oldPin, byte[] newPin) 330 | { 331 | byte Explicit = 0; 332 | byte[] data; 333 | if (oldPin == null) 334 | { 335 | data = newPin; 336 | Explicit = 1; 337 | } 338 | else 339 | { 340 | data = new byte[oldPin.Length + newPin.Length]; 341 | oldPin.CopyTo(data, 0); 342 | newPin.CopyTo(data, oldPin.Length); 343 | } 344 | return new Apdu(0x00, 0x24, Explicit, (byte)(pinID & 0x7F | (BackTracking ? 0x80 : 0)), data); 345 | } 346 | 347 | public static Apdu GetData(byte mode) 348 | { 349 | return new Apdu(0x00, 0xCA, 0x01, mode, 0x00); 350 | } 351 | 352 | public static byte[] ISOPad(byte[] data) 353 | { 354 | int padLen; 355 | if ((data.Length & 0x7) == 0) 356 | padLen = data.Length + 8; 357 | else 358 | padLen = data.Length - (data.Length & 0x7) + 0x08; 359 | 360 | byte[] padData = new byte[padLen]; 361 | data.CopyTo(padData, 0); 362 | padData[data.Length] = 0x80; 363 | for (int i = data.Length + 1; i < padData.Length; i++) 364 | padData[i] = 0; 365 | return padData; 366 | } 367 | public static byte[] ANSIPad(byte[] data) 368 | { 369 | int padLen; 370 | if ((data.Length & 0x7) == 0) 371 | padLen = data.Length; 372 | else 373 | padLen = (data.Length - (data.Length & 0x7) + 0x08); 374 | byte[] padData = new byte[padLen]; 375 | data.CopyTo(padData, 0); 376 | for (int i = data.Length; i < padData.Length; i++) 377 | padData[i] = 0; 378 | return padData; 379 | } 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /Driver/BixVReader-Cert.cer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCob/PIVert/eb435eb86217748e2b4f6f5adc09ce66600f18b1/Driver/BixVReader-Cert.cer -------------------------------------------------------------------------------- /Driver/BixVReaderInstaller.msi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCob/PIVert/eb435eb86217748e2b4f6f5adc09ce66600f18b1/Driver/BixVReaderInstaller.msi -------------------------------------------------------------------------------- /DriverCom/NullHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace VirtualSmartCard.DriverCom 7 | { 8 | public interface IDriverCom 9 | { 10 | event Action log; 11 | void Log(object logMsg); 12 | void Log(byte[] logMsg); 13 | 14 | ReaderSettings Settings { get; } 15 | void Start(); 16 | void Stop(); 17 | 18 | bool CardInserted { get; set; } 19 | event Action CardInsert; 20 | 21 | bool DriverConnected { get; set; } 22 | event Action DriverConnect; 23 | 24 | ICardHandler Handler { set; } 25 | } 26 | 27 | class NullHandler : ICardHandler 28 | { 29 | public byte[] ProcessApdu(byte[] apdu) 30 | { 31 | return null; 32 | } 33 | 34 | public byte[] ResetCard(bool warm) 35 | { 36 | return null; 37 | } 38 | 39 | public byte[] ATR 40 | { 41 | get { return null; } 42 | } 43 | 44 | public bool IsCardInserted 45 | { 46 | get { return false; } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /DriverCom/PipeCom.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.IO.Pipes; 7 | using System.IO; 8 | 9 | namespace VirtualSmartCard.DriverCom 10 | { 11 | public class PipeCom : IDriverCom 12 | { 13 | public ICardHandler handler; 14 | public ICardHandler Handler { set { handler = value; } } 15 | 16 | public event Action log; 17 | public event Action DriverConnect; 18 | public event Action CardInsert; 19 | 20 | bool cardInserted = false; 21 | public bool CardInserted 22 | { 23 | get { return cardInserted; } 24 | set 25 | { 26 | if (cardInserted != value && DriverConnected) 27 | { 28 | if (value && handler.ATR == null) 29 | { 30 | cardInserted = false; 31 | return; 32 | } 33 | if (SetCardInserted(value)) 34 | { 35 | cardInserted = value; 36 | if (CardInsert != null) 37 | CardInsert(value); 38 | } 39 | } 40 | } 41 | } 42 | 43 | bool driverConnected = false; 44 | public bool DriverConnected 45 | { 46 | get { return driverConnected; } 47 | set 48 | { 49 | if (driverConnected != value) 50 | { 51 | driverConnected = value; 52 | if (DriverConnect != null) 53 | DriverConnect(value); 54 | } 55 | } 56 | } 57 | 58 | 59 | public void Log(object logMsg) 60 | { 61 | if (log != null) 62 | log(logMsg); 63 | } 64 | 65 | public void Log(byte[] logMsg) 66 | { 67 | if (log != null) 68 | log(ByteArray.hexDump(logMsg)); 69 | } 70 | 71 | NamedPipeClientStream pipe; 72 | NamedPipeClientStream eventPipe; 73 | BinaryWriter bwEventPipe; 74 | 75 | Thread driverThread; 76 | bool running = true; 77 | 78 | public ReaderSettings Settings 79 | { 80 | get { return settings; } 81 | } 82 | 83 | public void Start() { 84 | if (settings != null) 85 | { 86 | driverThread = new Thread(new ThreadStart(Client)); 87 | driverThread.Start(); 88 | } 89 | else 90 | throw new Exception("Connection parameters not set"); 91 | } 92 | 93 | PipeReaderSettings settings = null; 94 | public PipeCom(PipeReaderSettings settings) 95 | { 96 | this.settings = settings; 97 | handler = new NullHandler(); 98 | } 99 | 100 | bool SetCardInserted(bool inserted) 101 | { 102 | Int32 command; 103 | if (inserted) 104 | { 105 | Log("Card Inserted"); 106 | command = 1; 107 | } 108 | else 109 | { 110 | Log("Card Removed"); 111 | command = 0; 112 | } 113 | bwEventPipe.Write(command); 114 | bwEventPipe.Flush(); 115 | eventPipe.WaitForPipeDrain(); 116 | return true; 117 | } 118 | public void Stop() 119 | { 120 | running = false; 121 | try 122 | { 123 | pipe.Flush(); 124 | pipe.WaitForPipeDrain(); 125 | pipe.Close(); 126 | } 127 | catch { } 128 | try 129 | { 130 | eventPipe.Flush(); 131 | eventPipe.WaitForPipeDrain(); 132 | eventPipe.Close(); 133 | } 134 | catch { } 135 | driverThread.Join(); 136 | } 137 | 138 | void Client() 139 | { 140 | running = true; 141 | try 142 | { 143 | while (running) 144 | { 145 | pipe = new NamedPipeClientStream(settings.Host, settings.PipeName, PipeDirection.InOut, PipeOptions.Asynchronous); 146 | eventPipe = new NamedPipeClientStream(settings.Host, settings.EventPipeName, PipeDirection.InOut, PipeOptions.Asynchronous); 147 | while (running) 148 | { 149 | try { pipe.Connect(2000); } 150 | catch(Exception e) 151 | { continue; } 152 | break; 153 | } 154 | 155 | Console.WriteLine("[=] Connected to Smartcard Data Pipe"); 156 | 157 | if (!running) 158 | { 159 | if (pipe.IsConnected) 160 | pipe.Close(); 161 | break; 162 | } 163 | while (running) 164 | { 165 | try { eventPipe.Connect(2000); } 166 | catch { continue; } 167 | break; 168 | } 169 | if (!running) 170 | { 171 | if (eventPipe.IsConnected) 172 | eventPipe.Close(); 173 | break; 174 | } 175 | 176 | Console.WriteLine("[=] Connected to Smartcard Event Pipe"); 177 | BinaryReader brPipe = new BinaryReader(pipe); 178 | BinaryWriter bwPipe = new BinaryWriter(pipe); 179 | bwEventPipe = new BinaryWriter(eventPipe); 180 | DriverConnected = true; 181 | try 182 | { 183 | while (running) 184 | { 185 | try 186 | { 187 | int command = brPipe.ReadInt32(); 188 | switch (command) 189 | { 190 | case 0: 191 | handler.ResetCard(true); 192 | Log("Reset"); 193 | if (cardInserted) 194 | { 195 | var ATR = handler.ATR; 196 | bwPipe.Write((Int32)ATR.Length); 197 | bwPipe.Write(ATR, 0, ATR.Length); 198 | bwPipe.Flush(); 199 | } 200 | else 201 | { 202 | bwPipe.Write((Int32)0); 203 | bwPipe.Flush(); 204 | } 205 | break; 206 | case 1: 207 | if (cardInserted) 208 | { 209 | var ATR = handler.ATR; 210 | bwPipe.Write((Int32)ATR.Length); 211 | bwPipe.Write(ATR, 0, ATR.Length); 212 | bwPipe.Flush(); 213 | } 214 | else 215 | { 216 | bwPipe.Write((Int32)0); 217 | bwPipe.Flush(); 218 | } 219 | //if (command == 0) 220 | // logMessage("Reset"); 221 | //else 222 | // logMessage("getATR"); 223 | break; 224 | case 2: 225 | 226 | 227 | 228 | int apduLen = brPipe.ReadInt32(); 229 | byte[] APDU = new byte[apduLen]; 230 | brPipe.Read(APDU, 0, apduLen); 231 | 232 | Log($"PDU: {ByteArray.hexDump(APDU)}"); 233 | 234 | byte[] resp = handler.ProcessApdu(APDU); 235 | 236 | Log($"Response: {ByteArray.hexDump(resp)}"); 237 | 238 | if (resp != null) 239 | { 240 | bwPipe.Write((Int32)resp.Length); 241 | bwPipe.Write(resp, 0, resp.Length); 242 | } 243 | else 244 | bwPipe.Write((Int32)0); 245 | bwPipe.Flush(); 246 | break; 247 | } 248 | } 249 | catch (Exception e) 250 | { 251 | if (!(e is EndOfStreamException) && !(e is ObjectDisposedException)) 252 | Log(e.ToString()); 253 | if (running) 254 | { 255 | break; 256 | } 257 | else 258 | { 259 | Log("Card Stop"); 260 | return; 261 | } 262 | } 263 | } 264 | } 265 | catch (Exception ex) 266 | { 267 | int p = 0; 268 | } 269 | finally 270 | { 271 | if (cardInserted) 272 | { 273 | cardInserted = false; 274 | if (CardInsert != null) 275 | CardInsert(false); 276 | } 277 | DriverConnected = false; 278 | if (pipe.IsConnected) 279 | pipe.Close(); 280 | if (eventPipe.IsConnected) 281 | eventPipe.Close(); 282 | pipe.Dispose(); 283 | eventPipe.Dispose(); 284 | } 285 | } 286 | } 287 | catch (Exception ex) 288 | { 289 | Log("Card Exception:" + ex.ToString()); 290 | } 291 | } 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /DriverCom/SocketCom.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Net.Sockets; 7 | using System.IO; 8 | 9 | namespace VirtualSmartCard.DriverCom 10 | { 11 | public class SocketCom : IDriverCom 12 | { 13 | public ICardHandler handler; 14 | public ICardHandler Handler { set { handler = value; } } 15 | 16 | public event Action log; 17 | public event Action DriverConnect; 18 | public event Action CardInsert; 19 | 20 | bool cardInserted = false; 21 | public bool CardInserted 22 | { 23 | get { return cardInserted; } 24 | set 25 | { 26 | if (cardInserted != value && DriverConnected) 27 | { 28 | if (value && handler.ATR == null) 29 | { 30 | cardInserted = false; 31 | return; 32 | } 33 | try 34 | { 35 | if (SetCardInserted(value)) 36 | { 37 | cardInserted = value; 38 | if (CardInsert != null) 39 | CardInsert(value); 40 | } 41 | } 42 | catch (Exception ex) { 43 | Log(ex.ToString()); 44 | } 45 | } 46 | } 47 | } 48 | 49 | bool driverConnected = false; 50 | public bool DriverConnected 51 | { 52 | get { return driverConnected; } 53 | set 54 | { 55 | if (driverConnected != value) 56 | { 57 | driverConnected = value; 58 | //cardInserted = false; 59 | if (DriverConnect != null) 60 | DriverConnect(value); 61 | } 62 | } 63 | } 64 | public void Log(object logMsg) 65 | { 66 | if (log != null) 67 | log(logMsg); 68 | } 69 | 70 | public void Log(byte[] logMsg) 71 | { 72 | if (log != null) 73 | log(ByteArray.hexDump(logMsg)); 74 | } 75 | 76 | Socket socket; 77 | Socket eventSocket; 78 | BinaryWriter bwEventPipe; 79 | 80 | Thread driverThread; 81 | bool running = true; 82 | string readerName; 83 | 84 | public ReaderSettings Settings 85 | { 86 | get { return settings; } 87 | } 88 | 89 | public void Start() 90 | { 91 | if (settings != null) 92 | { 93 | driverThread = new Thread(new ThreadStart(Client)); 94 | driverThread.Start(); 95 | } 96 | else 97 | throw new Exception("Connection parameters not set"); 98 | } 99 | 100 | TcpIpReaderSettings settings = null; 101 | public SocketCom(TcpIpReaderSettings settings) 102 | { 103 | handler = new NullHandler(); 104 | this.settings = settings; 105 | } 106 | 107 | bool SetCardInserted(bool inserted) 108 | { 109 | Int32 command; 110 | if (inserted) 111 | { 112 | Log("Card Inserted"); 113 | command = 1; 114 | } 115 | else 116 | { 117 | Log("Card Removed"); 118 | command = 0; 119 | } 120 | bwEventPipe.Write(command); 121 | bwEventPipe.Flush(); 122 | return true; 123 | } 124 | 125 | public void Stop() 126 | { 127 | running = false; 128 | try 129 | { 130 | socket.Disconnect(false); 131 | socket.Close(); 132 | } 133 | catch { } 134 | try 135 | { 136 | eventSocket.Disconnect(false); 137 | eventSocket.Close(); 138 | } 139 | catch { } 140 | driverThread.Join(); 141 | } 142 | void Client() 143 | { 144 | running = true; 145 | while (running) 146 | { 147 | socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 148 | eventSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 149 | while (running) 150 | { 151 | try { socket.Connect(settings.Host, settings.Port); } 152 | catch { continue; } 153 | break; 154 | } 155 | if (!running) 156 | { 157 | if (socket.Connected) 158 | socket.Close(); 159 | break; 160 | } 161 | while (running) 162 | { 163 | try { eventSocket.Connect(settings.Host, settings.EventPort); } 164 | catch { continue; } 165 | break; 166 | } 167 | if (!running) 168 | { 169 | if (eventSocket.Connected) 170 | eventSocket.Close(); 171 | break; 172 | } 173 | var socketStream = new NetworkStream(socket); 174 | var eventSocketStream = new NetworkStream(eventSocket); 175 | BinaryReader brPipe = new BinaryReader(socketStream); 176 | BinaryWriter bwPipe = new BinaryWriter(socketStream); 177 | bwEventPipe = new BinaryWriter(eventSocketStream); 178 | DriverConnected = true; 179 | try 180 | { 181 | while (running) 182 | { 183 | try 184 | { 185 | int command = brPipe.ReadInt32(); 186 | switch (command) 187 | { 188 | case 0: 189 | handler.ResetCard(true); 190 | Log("Reset"); 191 | if (cardInserted) 192 | { 193 | var ATR = handler.ATR; 194 | bwPipe.Write((Int32)ATR.Length); 195 | bwPipe.Write(ATR, 0, ATR.Length); 196 | bwPipe.Flush(); 197 | } 198 | else 199 | { 200 | bwPipe.Write((Int32)0); 201 | bwPipe.Flush(); 202 | } 203 | break; 204 | case 1: 205 | if (cardInserted) 206 | { 207 | var ATR = handler.ATR; 208 | bwPipe.Write((Int32)ATR.Length); 209 | bwPipe.Write(ATR, 0, ATR.Length); 210 | bwPipe.Flush(); 211 | } 212 | else 213 | { 214 | bwPipe.Write((Int32)0); 215 | bwPipe.Flush(); 216 | } 217 | break; 218 | case 2: 219 | 220 | int apduLen = brPipe.ReadInt32(); 221 | byte[] APDU = new byte[apduLen]; 222 | brPipe.Read(APDU, 0, apduLen); 223 | byte[] resp = handler.ProcessApdu(APDU); 224 | 225 | if (resp != null) 226 | { 227 | bwPipe.Write((Int32)resp.Length); 228 | bwPipe.Write(resp, 0, resp.Length); 229 | } 230 | else 231 | bwPipe.Write((Int32)0); 232 | bwPipe.Flush(); 233 | break; 234 | } 235 | } 236 | catch (Exception e) 237 | { 238 | if (!(e is EndOfStreamException) && !(e is ObjectDisposedException) && !(e is IOException)) 239 | Log(e.ToString()); 240 | if (running) 241 | { 242 | break; 243 | } 244 | else 245 | { 246 | Log("Card Stop"); 247 | return; 248 | } 249 | } 250 | } 251 | } 252 | finally 253 | { 254 | if (cardInserted) 255 | { 256 | cardInserted = false; 257 | if (CardInsert != null) 258 | CardInsert(false); 259 | } 260 | DriverConnected = false; 261 | if (socket.Connected) 262 | socket.Close(); 263 | if (eventSocket.Connected) 264 | eventSocket.Close(); 265 | } 266 | } 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /Interfaces.cs: -------------------------------------------------------------------------------- 1 | namespace VirtualSmartCard { 2 | public interface ICardHandler 3 | { 4 | byte[] ProcessApdu(byte[] apdu); 5 | byte[] ResetCard(bool warm); 6 | byte[] ATR { get; } 7 | } 8 | } -------------------------------------------------------------------------------- /PIVCardHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Linq; 4 | using VirtualSmartCard; 5 | using ISO7816; 6 | using System.IO; 7 | using Yubico.Core.Tlv; 8 | using Yubico.YubiKey.Piv.Objects; 9 | using Org.BouncyCastle.Crypto.Engines; 10 | using Org.BouncyCastle.Pkcs; 11 | using Org.BouncyCastle.Crypto.Parameters; 12 | 13 | namespace PIVert { 14 | 15 | public class PIVCardHandler : ICardHandler { 16 | 17 | enum DataObjectType { 18 | DiscoveryObject = 0x7e, 19 | CardCapabilityContainer = 0x5fc107, 20 | CardHolderUniqieID = 0x5fc102, 21 | CardHolderFingerPrints = 0x5fc103, 22 | CardHolderFacialImage = 0x5fc108, 23 | CertPIVAuth = 0x5fc105, 24 | CertCardAuth = 0x5fc101, 25 | CertSign = 0x5fc10a, 26 | CertKeyMan = 0x5fc10b, 27 | SecurityObject = 0x5fc106, 28 | PrintedInfo = 0x5fc109, 29 | KeyHistory = 0x005FC10C 30 | } 31 | 32 | static readonly byte[] PIVAID = new byte[] { 0xa0, 0x00, 0x00, 0x03, 0x08, 0x00, 0x00, 0x10, 0x00 }; 33 | static readonly byte[] DataObjectNotFound = new byte[] { 0x6a, 0x82 }; 34 | static readonly byte[] ErrorUnchanged = new byte[] { 0x61, 00 }; 35 | static readonly byte[] StatusOK = new byte[] { 0x90, 00 }; 36 | static readonly byte[] InstructionNotSupported = new byte[] { 0x6d, 00 }; 37 | static readonly byte[] PIVUsagePolicy = new byte[] { 0x40, 00 }; 38 | 39 | public byte[] ATR => new byte[] { 0x3B, 0x9F, 0x95, 0x81, 0x31, 0xFE, 0x9F, 0x00, 0x66, 0x46, 0x53, 0x05, 0x10, 0x00, 0x11, 0x71, 0xDF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }; 40 | 41 | readonly CardCapabilityContainer cardCapabilityContainer = new CardCapabilityContainer(); 42 | readonly CardholderUniqueId cardHolderUID = new CardholderUniqueId(); 43 | readonly Pkcs12Store store = new Pkcs12Store(); 44 | readonly RsaPrivateCrtKeyParameters key; 45 | readonly byte[] certificateBytes; 46 | 47 | BinaryReader pendingResponse; 48 | MemoryStream pendingRequest; 49 | 50 | public PIVCardHandler(string pfxPath, string pfxPassword) { 51 | cardCapabilityContainer.SetRandomCardId(); 52 | cardHolderUID.SetRandomGuid(); 53 | 54 | store.Load(new MemoryStream(File.ReadAllBytes(pfxPath)),pfxPassword.ToArray()); 55 | 56 | foreach(var alias in store.Aliases.Cast()) { 57 | if (store.IsKeyEntry(alias)) { 58 | key = (RsaPrivateCrtKeyParameters)store.GetKey(alias).Key; 59 | certificateBytes = store.GetCertificate(alias).Certificate.GetEncoded(); 60 | } 61 | } 62 | 63 | if(key == null) { 64 | throw new ArgumentException("Could not find any private keys inside the PFX"); 65 | } 66 | } 67 | 68 | public int GetInteger(ReadOnlySpan data) { 69 | int result = 0; 70 | for(int idx=0; idx 0xff) { 85 | 86 | pendingResponse = new BinaryReader(new MemoryStream(data)); 87 | var response = pendingResponse.ReadBytes(0xff); 88 | var remaining = Math.Min(0xff, pendingResponse.BaseStream.Length - pendingResponse.BaseStream.Position); 89 | return response.Concat(new byte[] { 0x61, (byte)remaining }).ToArray(); 90 | 91 | } else { 92 | return data.Concat(StatusOK).ToArray(); 93 | } 94 | } 95 | 96 | public byte[] ProcessApdu(byte[] apdu) { 97 | 98 | var apduObj = new Apdu(apdu); 99 | bool chaining = (apduObj.CLA & 0x10) == 0x10; 100 | 101 | if (apduObj.CLA == 0 || apduObj.CLA == 0x10) { 102 | 103 | if (apduObj.INS == 0xa4 && apduObj.P1 == 0x4) { 104 | 105 | var aid = apduObj.Data; 106 | if (aid.SequenceEqual(PIVAID)) { 107 | 108 | var tlvResponse = new TlvWriter(); 109 | 110 | using (tlvResponse.WriteNestedTlv(0x61)) { 111 | tlvResponse.WriteValue(0x4f, new byte[] { 0x00, 0x00, 0x10, 0x00, 0x01, 0x00 }); 112 | using (tlvResponse.WriteNestedTlv(0x79)) { 113 | tlvResponse.WriteValue(0x4f, PIVAID); 114 | } 115 | tlvResponse.WriteString(0x50, "PIVert PIV Applet", Encoding.ASCII); 116 | using (tlvResponse.WriteNestedTlv(0xac)) { 117 | tlvResponse.WriteByte(0x80, 0x3); 118 | tlvResponse.WriteByte(0x80, 0x8); 119 | tlvResponse.WriteByte(0x80, 0xa); 120 | tlvResponse.WriteByte(0x80, 0xc); 121 | tlvResponse.WriteByte(0x80, 0x6); 122 | tlvResponse.WriteByte(0x80, 0x7); 123 | tlvResponse.WriteByte(0x80, 0x11); 124 | tlvResponse.WriteByte(0x80, 0x14); 125 | tlvResponse.WriteByte(0x6, 0); 126 | } 127 | } 128 | 129 | return tlvResponse.Encode().Concat(StatusOK).ToArray(); 130 | 131 | } else { 132 | return DataObjectNotFound; 133 | } 134 | 135 | } else if (apduObj.INS == 0x20 && (apduObj.P1 == 0x00 || apduObj.P1 == 0xff) && apduObj.P2 == 0x80) { 136 | 137 | Console.WriteLine($"[+] Received verify PIN APDU, allowing any PIN"); 138 | return StatusOK; 139 | 140 | }else if(apduObj.INS == 0x87) { 141 | 142 | byte[] data; 143 | 144 | if (chaining) { 145 | if(pendingRequest == null) { 146 | pendingRequest = new MemoryStream(); 147 | } 148 | pendingRequest.Write(apduObj.Data, 0, apduObj.Data.Length); 149 | return StatusOK; 150 | } else { 151 | 152 | if(pendingRequest != null) { 153 | pendingRequest.Write(apduObj.Data, 0, apduObj.Data.Length); 154 | data = pendingRequest.ToArray(); 155 | } else { 156 | data = apduObj.Data; 157 | } 158 | 159 | var tlvRequests = new TlvReader(data); 160 | var authRequest = tlvRequests.ReadNestedTlv(0x7c); 161 | authRequest.ReadValue(0x82); 162 | var signData = authRequest.ReadValue(0x81).ToArray(); 163 | var signature = SignData(signData); 164 | var tlvResponse = new TlvWriter(); 165 | 166 | using (tlvResponse.WriteNestedTlv(0x7c)) { 167 | tlvResponse.WriteValue(0x82, signature); 168 | } 169 | 170 | Console.WriteLine($"[+] Authenticate APDU recevied, signed message with lengh {signData.Length} bytes"); 171 | 172 | pendingRequest.Close(); 173 | pendingRequest = null; 174 | return GenerateLargeResponse(tlvResponse.Encode()); 175 | } 176 | 177 | 178 | }else if(apduObj.INS == 0xc0 && apduObj.P1 == 0 && apduObj.P2 == 0) { 179 | 180 | if(pendingResponse != null) { 181 | 182 | var response = pendingResponse.ReadBytes(0xff); 183 | var remaining = Math.Min(0xff, pendingResponse.BaseStream.Length - pendingResponse.BaseStream.Position); 184 | 185 | if(remaining == 0) { 186 | pendingResponse.Close(); 187 | pendingResponse = null; 188 | return response.Concat(StatusOK).ToArray(); 189 | } else { 190 | 191 | return response.Concat(new byte[] { 0x61, (byte)remaining }).ToArray(); 192 | } 193 | 194 | } else { 195 | 196 | return ErrorUnchanged; 197 | } 198 | 199 | } else if (apduObj.INS == 0xcb && apduObj.P1 == 0x3f && apduObj.P2 == 0xff) { 200 | 201 | var tlvRequest = new TlvReader(apduObj.Data); 202 | var tlvResponse = new TlvWriter(); 203 | var dataObjectType = (DataObjectType)GetInteger(tlvRequest.ReadValue(0x5c).Span); 204 | 205 | Console.WriteLine($"[=] Request for PIV DataObject: {dataObjectType}"); 206 | 207 | switch (dataObjectType) { 208 | case DataObjectType.DiscoveryObject: 209 | 210 | using (tlvResponse.WriteNestedTlv(0x7e)) { 211 | tlvResponse.WriteValue(0x4f, PIVAID); 212 | tlvResponse.WriteValue(0x5f2f, PIVUsagePolicy); 213 | }; 214 | 215 | return tlvResponse.Encode().Concat(StatusOK).ToArray(); 216 | 217 | case DataObjectType.CardCapabilityContainer: 218 | 219 | return cardCapabilityContainer.Encode().Concat(StatusOK).ToArray(); 220 | 221 | case DataObjectType.CardHolderUniqieID: 222 | 223 | return cardHolderUID.Encode().Concat(StatusOK).ToArray(); 224 | 225 | case DataObjectType.CertPIVAuth: 226 | case DataObjectType.CertCardAuth: 227 | case DataObjectType.CertSign: 228 | 229 | using (tlvResponse.WriteNestedTlv(0x53)) { 230 | tlvResponse.WriteValue(0x70, certificateBytes); 231 | tlvResponse.WriteValue(0x71, new byte[] {00}); 232 | tlvResponse.WriteValue(0xFE, new byte[] { }); 233 | }; 234 | 235 | return GenerateLargeResponse(tlvResponse.Encode()); 236 | 237 | default: 238 | 239 | return DataObjectNotFound; 240 | } 241 | } 242 | } 243 | 244 | Console.WriteLine($"[=] Unsupported INS {apduObj.INS:x}, CLA {apduObj.CLA:x}, P1 {apduObj.P1:x}, P2 {apduObj.P2:x}"); 245 | return InstructionNotSupported; 246 | } 247 | 248 | public byte[] ResetCard(bool warm) { 249 | return ATR; 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /PIVert.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net48 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | PreserveNewest 35 | 36 | 37 | Always 38 | 39 | 40 | PreserveNewest 41 | 42 | 43 | 44 | 45 | 8.0 46 | 47 | 48 | 49 | AnyCPU 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /PIVert.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.32602.291 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PIVert", "PIVert.csproj", "{8E34C1C3-D7BE-4B02-8273-488D82A9B0E9}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {8E34C1C3-D7BE-4B02-8273-488D82A9B0E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {8E34C1C3-D7BE-4B02-8273-488D82A9B0E9}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {8E34C1C3-D7BE-4B02-8273-488D82A9B0E9}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {8E34C1C3-D7BE-4B02-8273-488D82A9B0E9}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {8739D1F8-CD67-4D6A-8D7A-E672683DDD52} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using System; 3 | using System.IO; 4 | using System.Reflection; 5 | using System.Runtime.InteropServices; 6 | using System.Security.Cryptography.X509Certificates; 7 | using System.Threading; 8 | using VirtualSmartCard; 9 | using VirtualSmartCard.DriverCom; 10 | 11 | namespace PIVert { 12 | class Program { 13 | 14 | static PipeCom pipeCom; 15 | static ManualResetEvent driverConnected = new ManualResetEvent(false); 16 | static bool verbose = false; 17 | static string readerConfig = @"[Driver] 18 | NumReaders=1 19 | 20 | [Reader0] 21 | RPC_TYPE=0 22 | VENDOR_NAME=Virtual Smart Card 23 | VENDOR_IFD_TYPE = Pipe Reader 24 | DECIVE_UNIT = 0 25 | "; 26 | 27 | public enum INSTALLMESSAGE { 28 | INSTALLMESSAGE_FATALEXIT = 0x00000000, // premature termination, possibly fatal OOM 29 | INSTALLMESSAGE_ERROR = 0x01000000, // formatted error message 30 | INSTALLMESSAGE_WARNING = 0x02000000, // formatted warning message 31 | INSTALLMESSAGE_USER = 0x03000000, // user request message 32 | INSTALLMESSAGE_INFO = 0x04000000, // informative message for log 33 | INSTALLMESSAGE_FILESINUSE = 0x05000000, // list of files in use that need to be replaced 34 | INSTALLMESSAGE_RESOLVESOURCE = 0x06000000, // request to determine a valid source location 35 | INSTALLMESSAGE_OUTOFDISKSPACE = 0x07000000, // insufficient disk space message 36 | INSTALLMESSAGE_ACTIONSTART = 0x08000000, // start of action: action name & description 37 | INSTALLMESSAGE_ACTIONDATA = 0x09000000, // formatted data associated with individual action item 38 | INSTALLMESSAGE_PROGRESS = 0x0A000000, // progress gauge info: units so far, total 39 | INSTALLMESSAGE_COMMONDATA = 0x0B000000, // product info for dialog: language Id, dialog caption 40 | INSTALLMESSAGE_INITIALIZE = 0x0C000000, // sent prior to UI initialization, no string data 41 | INSTALLMESSAGE_TERMINATE = 0x0D000000, // sent after UI termination, no string data 42 | INSTALLMESSAGE_SHOWDIALOG = 0x0E000000 // sent prior to display or authored dialog or wizard 43 | } 44 | 45 | public enum INSTALLLOGMODE // bit flags for use with MsiEnableLog and MsiSetExternalUI 46 | { 47 | INSTALLLOGMODE_FATALEXIT = (1 << (INSTALLMESSAGE.INSTALLMESSAGE_FATALEXIT >> 24)), 48 | INSTALLLOGMODE_ERROR = (1 << (INSTALLMESSAGE.INSTALLMESSAGE_ERROR >> 24)), 49 | INSTALLLOGMODE_WARNING = (1 << (INSTALLMESSAGE.INSTALLMESSAGE_WARNING >> 24)), 50 | INSTALLLOGMODE_USER = (1 << (INSTALLMESSAGE.INSTALLMESSAGE_USER >> 24)), 51 | INSTALLLOGMODE_INFO = (1 << (INSTALLMESSAGE.INSTALLMESSAGE_INFO >> 24)), 52 | INSTALLLOGMODE_RESOLVESOURCE = (1 << (INSTALLMESSAGE.INSTALLMESSAGE_RESOLVESOURCE >> 24)), 53 | INSTALLLOGMODE_OUTOFDISKSPACE = (1 << (INSTALLMESSAGE.INSTALLMESSAGE_OUTOFDISKSPACE >> 24)), 54 | INSTALLLOGMODE_ACTIONSTART = (1 << (INSTALLMESSAGE.INSTALLMESSAGE_ACTIONSTART >> 24)), 55 | INSTALLLOGMODE_ACTIONDATA = (1 << (INSTALLMESSAGE.INSTALLMESSAGE_ACTIONDATA >> 24)), 56 | INSTALLLOGMODE_COMMONDATA = (1 << (INSTALLMESSAGE.INSTALLMESSAGE_COMMONDATA >> 24)), 57 | INSTALLLOGMODE_PROPERTYDUMP = (1 << (INSTALLMESSAGE.INSTALLMESSAGE_PROGRESS >> 24)), // log only 58 | INSTALLLOGMODE_VERBOSE = (1 << (INSTALLMESSAGE.INSTALLMESSAGE_INITIALIZE >> 24)), // log only 59 | INSTALLLOGMODE_EXTRADEBUG = (1 << (INSTALLMESSAGE.INSTALLMESSAGE_TERMINATE >> 24)), // log only 60 | INSTALLLOGMODE_LOGONLYONERROR = (1 << (INSTALLMESSAGE.INSTALLMESSAGE_SHOWDIALOG >> 24)), // log only 61 | INSTALLLOGMODE_PROGRESS = (1 << (INSTALLMESSAGE.INSTALLMESSAGE_PROGRESS >> 24)), // external handler only 62 | INSTALLLOGMODE_INITIALIZE = (1 << (INSTALLMESSAGE.INSTALLMESSAGE_INITIALIZE >> 24)), // external handler only 63 | INSTALLLOGMODE_TERMINATE = (1 << (INSTALLMESSAGE.INSTALLMESSAGE_TERMINATE >> 24)), // external handler only 64 | INSTALLLOGMODE_SHOWDIALOG = (1 << (INSTALLMESSAGE.INSTALLMESSAGE_SHOWDIALOG >> 24)), // external handler only 65 | INSTALLLOGMODE_FILESINUSE = (1 << (INSTALLMESSAGE.INSTALLMESSAGE_FILESINUSE >> 24)), // external handler only 66 | } 67 | 68 | public enum INSTALLLOGATTRIBUTES // flag attributes for MsiEnableLog 69 | { 70 | INSTALLLOGATTRIBUTES_APPEND = (1 << 0), 71 | INSTALLLOGATTRIBUTES_FLUSHEACHLINE = (1 << 1), 72 | } 73 | 74 | 75 | [DllImport("msi.dll", CharSet = CharSet.Auto, SetLastError = true)] 76 | static extern uint MsiInstallProduct(string packagePath, string commandLine); 77 | 78 | [DllImport("msi.dll", CharSet = CharSet.Auto, SetLastError = true)] 79 | public static extern uint MsiEnableLog(INSTALLLOGMODE dwLogMode, string szLogFile, INSTALLLOGATTRIBUTES dwLogAttributes); 80 | 81 | 82 | 83 | static void InstallCert(StoreName storeName, string file) { 84 | var cert = new X509Certificate2(File.ReadAllBytes(file)); 85 | var store = new X509Store(storeName, StoreLocation.LocalMachine); 86 | store.Open(OpenFlags.ReadWrite); 87 | store.Add(cert); 88 | store.Close(); 89 | 90 | store = new X509Store(storeName, StoreLocation.CurrentUser); 91 | store.Open(OpenFlags.ReadWrite); 92 | store.Add(cert); 93 | store.Close(); 94 | } 95 | 96 | 97 | 98 | static void PrintUsage() { 99 | Console.WriteLine("Usage: PIVert.exe install | pfx_file [pfx_password]"); 100 | return; 101 | } 102 | 103 | static void InstallDriver() { 104 | 105 | try { 106 | var driverFolder = Path.Combine(Path.GetDirectoryName(Path.Combine(Assembly.GetEntryAssembly().Location)), "Driver"); 107 | 108 | var allowAnyEKU = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\SmartCardCredentialProvider", "AllowCertificatesWithNoEKU", 0); 109 | if (allowAnyEKU == null || ((int)allowAnyEKU) == 0) { 110 | Console.WriteLine("[=] AllowCertificatesWithNoEKU on SmartCard Credential Provider not set, enabling..."); 111 | Registry.SetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\SmartCardCredentialProvider", "AllowCertificatesWithNoEKU", 1); 112 | Console.WriteLine("[+] Enabled AllowCertificatesWithNoEKU on SmartCard Credential Provider"); 113 | } 114 | 115 | var strictKDC = Registry.GetValue(@"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos\Parameters", "KdcValidation", 2); 116 | if(strictKDC != null && (int)strictKDC == 2) { 117 | Registry.SetValue(@"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos\Parameters", "KdcValidation", 0); 118 | Console.WriteLine("[+] Disabling strict KDC validation"); 119 | } 120 | 121 | var disableCRL = Registry.GetValue(@"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos\Parameters", "UseCachedCRLOnlyAndIgnoreRevocationUnknownErrors", 0); 122 | if (disableCRL == null || (disableCRL != null && (int)disableCRL == 0)) { 123 | Registry.SetValue(@"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos\Parameters", "UseCachedCRLOnlyAndIgnoreRevocationUnknownErrors", 1); 124 | Console.WriteLine("[+] Disabling KDC CRL certificate validation"); 125 | } 126 | 127 | 128 | 129 | Console.WriteLine("[=] Writing BixVReader.ini config to C:\\Windows"); 130 | File.WriteAllText(@"C:\Windows\BixVReader.ini", readerConfig); 131 | Console.WriteLine("[=] Installing driver signing certificate into Root and Trusted Publishers local machine store"); 132 | InstallCert(StoreName.Root, Path.Combine(driverFolder, "BixVReader-Cert.cer")); 133 | InstallCert(StoreName.TrustedPublisher, Path.Combine(driverFolder, "BixVReader-Cert.cer")); 134 | Console.WriteLine("[=] Installing driver MSI"); 135 | 136 | MsiEnableLog(INSTALLLOGMODE.INSTALLLOGMODE_INFO, Path.Combine(driverFolder, "install.log"), 0); 137 | uint result = MsiInstallProduct(Path.Combine(driverFolder, "BixVReaderInstaller.msi"), ""); 138 | 139 | if (result == 0) { 140 | Console.WriteLine("[+] Installer completed"); 141 | } else { 142 | Console.WriteLine($"[!] Failed to install MSI with error {result}"); 143 | } 144 | 145 | }catch(Exception e) { 146 | Console.WriteLine($"[!] Failed to install the virtual smart card reader device: {e.Message}"); 147 | Console.WriteLine(e.StackTrace); 148 | } 149 | } 150 | 151 | static void RunEmulation(string pfxFile, string pfxPassword) { 152 | 153 | var cardSettings = (PipeReaderSettings)ReaderSettings.LocalPipe(); 154 | 155 | pipeCom = new PipeCom(cardSettings); 156 | pipeCom.Handler = new PIVCardHandler(pfxFile, pfxPassword); 157 | pipeCom.DriverConnect += PipeCom_DriverConnect; 158 | pipeCom.CardInsert += PipeCom_CardInsert; 159 | pipeCom.log += PipeCom_log; 160 | pipeCom.Start(); 161 | if (driverConnected.WaitOne(10000)) { 162 | 163 | Thread.Sleep(1000); 164 | pipeCom.CardInserted = true; 165 | 166 | Console.WriteLine("[=] Press ESC to exit, or any other key to remove and reinsert the virtual card?"); 167 | 168 | while (Console.ReadKey(true).Key != ConsoleKey.Escape) { 169 | pipeCom.CardInserted = false; 170 | Thread.Sleep(1000); 171 | pipeCom.CardInserted = true; 172 | } 173 | 174 | pipeCom.CardInserted = false; 175 | pipeCom.DriverConnected = false; 176 | pipeCom.Stop(); 177 | 178 | } else { 179 | Console.WriteLine("[!] Failed to connect to Virtual Smart Card, is the driver installed?"); 180 | } 181 | 182 | } 183 | 184 | 185 | static void Main(string[] args) { 186 | 187 | if(args.Length == 0) { 188 | PrintUsage(); 189 | return; 190 | } 191 | 192 | if(args[0] == "install") { 193 | InstallDriver(); 194 | } else { 195 | RunEmulation(args[0], args.Length == 1 ? "" : args[1]); 196 | } 197 | } 198 | 199 | private static void PipeCom_CardInsert(bool inserted) { 200 | Console.WriteLine($"[+] Virtual card {(inserted ? "inserted" : "removed")}"); 201 | } 202 | 203 | private static void PipeCom_log(object obj) { 204 | if(verbose) 205 | Console.WriteLine(obj); 206 | } 207 | 208 | private static void PipeCom_DriverConnect(bool connected) { 209 | driverConnected.Set(); 210 | Console.WriteLine($"[+] {(connected ? "Connected" : "Disconnected")} Virtal Smart Card Driver"); 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "PIVert": { 4 | "commandName": "Project", 5 | "commandLineArgs": "install" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PIVert - PIV smart card emulator 2 | 3 | ## Introduction 4 | 5 | PIVert is a NIST SP 800-73 PIV smart card emulator. You can supply PIVert with a PFX file containing a certificate and corresponding private key and the tool will emulate the card to Windows as a genuine PIV card. The card can then be used for authentication over RDP, Citrix and VMWare Horizon using the build in smart card redirection feature. 6 | 7 | ## Usage 8 | 9 | ### Installation 10 | 11 | On a clean machine that hasn’t used PIVert before, the first step is to install the BixVReader virtual smart card reader driver. WARNING, this does install a self-signed trusted root certificate authority certificate since the driver is self-signed using a test certificate. The install step also sets the AllowCertificatesWithNoEKU group policy option. Without this option, only certificates with the Smartcard Logon EKU are offered for authentication. With this option enabled, certificates with the User Authentication EKU are also available for authentication. 12 | 13 | ``` 14 | .\PIVert.exe install 15 | [=] AllowCertificatesWithNoEKU on SmartCard Credential Provider not set, enabling... 16 | [+] Enabled AllowCertificatesWithNoEKU on SmartCard Credential Provider 17 | [=] Writing BixVReader.ini config to C:\Windows 18 | [=] Installing driver signing certificate into Root and Trusted Publishers local machine store 19 | [=] Installing driver MSI 20 | [+] Installer completed 21 | ``` 22 | 23 | ## Emulating a PIV Card 24 | 25 | To emulate a PIV card using a PFX file, you simply specify the PFX file and PFX password as command line arguments. 26 | 27 | ``` 28 | .\PIVert.exe .\Administrator.pfx password 29 | [=] Connected to Smartcard Data Pipe 30 | [=] Connected to Smartcard Event Pipe 31 | [+] Connected Virtal Smart Card Driver 32 | [+] Virtual card inserted 33 | [=] Press ESC to exit, or any other key to remove and reinsert the virtual card? 34 | [=] Unsupported INS ca with CLA 0 35 | [=] Request for PIV DataObject: CardHolderUniqieID 36 | [=] Request for PIV DataObject: CertPIVAuth 37 | [=] Request for PIV DataObject: CertSign 38 | [=] Request for PIV DataObject: CertKeyMan 39 | [=] Request for PIV DataObject: CertCardAuth 40 | [=] Request for PIV DataObject: KeyHistory 41 | [=] Request for PIV DataObject: CertPIVAuth 42 | [=] Request for PIV DataObject: CertSign 43 | [=] Request for PIV DataObject: CertCardAuth 44 | [=] Request for PIV DataObject: CardHolderUniqieID 45 | [=] Request for PIV DataObject: CertKeyMan 46 | [=] Request for PIV DataObject: KeyHistory 47 | ``` 48 | 49 | There does appear to be a bug in the driver (or potentially my code :D) were sometimes the virtual card insertion is not detected by Windows due issues reading the ATR from the virtual card. To combat this issue you can press any key other than ESC to virtually remove and re-insert the card. You’ll know when this as happened as you won’t see the requests for DataObjects on start-up like above. 50 | 51 | ## Acknowledgements 52 | 53 | * Fabio Ottavi for BixVReader UDMF driver ([https://www.codeproject.com/Articles/134010/An-UMDF-Driver-for-a-Virtual-Smart-Card-Reader]) 54 | * Frank Morgner and the Virtual Smart Card Project ([https://github.com/frankmorgner/vsmartcard/tree/master/virtualsmartcard]) 55 | * Yibico and the Yubikey .NET SDK which some code has made it's way into PIVert ([https://github.com/Yubico/Yubico.NET.SDK]) 56 | -------------------------------------------------------------------------------- /RemoteReaderSettings.cs: -------------------------------------------------------------------------------- 1 | namespace VirtualSmartCard { 2 | public abstract class ReaderSettings 3 | { 4 | public string Name { get; set; } 5 | public string Host { get; set; } 6 | public bool IsRemote { get; set; } 7 | 8 | static public ReaderSettings LocalPipe() { 9 | var result = new PipeReaderSettings(); 10 | result.Host = "."; 11 | result.IsRemote = false; 12 | result.Name = "LocalReader"; 13 | result.PipeName = "SCardSimulatorDriver0"; 14 | result.EventPipeName = "SCardSimulatorDriverEvents0"; 15 | return result; 16 | } 17 | 18 | 19 | public override string ToString() 20 | { 21 | if (IsRemote) 22 | return Host + "\\" + Name; 23 | else 24 | return Name; 25 | } 26 | } 27 | 28 | public class TcpIpReaderSettings : ReaderSettings 29 | { 30 | internal TcpIpReaderSettings() { } 31 | public int Port {get;set;} 32 | public int EventPort {get;set;} 33 | } 34 | 35 | public class PipeReaderSettings : ReaderSettings 36 | { 37 | internal PipeReaderSettings() { } 38 | public string PipeName { get; set; } 39 | public string EventPipeName { get; set; } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Yubikey/Cryptography/CryptographyProviders.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Yubico AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Security.Cryptography; 17 | 18 | namespace Yubico.YubiKey.Cryptography 19 | { 20 | /// 21 | /// This class contains properties that specify cryptographic providers. 22 | /// 23 | /// 24 | /// During the course of operations, the SDK will need to perform 25 | /// cryptographic operations, such as random number generation, HMAC, 26 | /// Triple-DES encryption, and so on. Any SDK operation that needs crypto 27 | /// will get it from this class. 28 | /// 29 | /// The properties in this class are delegates. They are functions that can 30 | /// build objects that will perform the required crypto. The reason these 31 | /// properties are methods that build objects, rather than objects 32 | /// themselves, is because the crypto classes implement IDisposable. 33 | /// In order to avoid the complex problem of ownership of an object that will 34 | /// be disposed once it goes out of scope, the SDK will create a new object 35 | /// each time one is needed. This new object will be in scope only for the 36 | /// duration of its use in the SDK, and will be disposed immediately when the 37 | /// SDK is done with it. 38 | /// 39 | /// 40 | /// This class will return default implementations, but can be replaced. That 41 | /// is, if you do nothing, the SDK will use default C# cryptography. If you 42 | /// want the SDK to use your own implementations, you can do so. See the 43 | /// User's Manual entry on 44 | /// alternate crypto implementations 45 | /// for a detailed description of replacing the defaults. Generally, you will 46 | /// simply need to set the appropriate property with a new function. 47 | /// 48 | /// 49 | /// Most applications will not replace the defaults, but for those that want 50 | /// the SDK to use a hardware RNG, hardware accelerator, or any other 51 | /// specific implementation for whatever reason, it is possible. 52 | /// 53 | /// 54 | public static class CryptographyProviders 55 | { 56 | /// 57 | /// This property is a delegate (function pointer). The method loaded 58 | /// will return an instance of RandomNumberGenerator. 59 | /// 60 | /// 61 | /// When an SDK operation needs random numbers it will generate them 62 | /// using an implementation of the 63 | /// System.Security.Cryptography.RandomNumberGenerator abstract 64 | /// class. However, when it needs the RNG class, it will ask this 65 | /// delegate to build an object, rather than ask for an object itself. 66 | /// This has to do with the fact that the RandomNumberGenerator 67 | /// class implements IDisposable. In order to avoid the complex 68 | /// problem of ownership of an object that will be disposed once it goes 69 | /// out of scope, the SDK will create a new object each time one is 70 | /// needed. This new object will be in scope only for the duration of its 71 | /// use in the SDK, and will be disposed immediately when the SDK is done 72 | /// with it. 73 | /// 74 | /// The method loaded will return an object. This class is initialized 75 | /// with a method that will build and return an instance of the C# 76 | /// default implementation. For example, it could be used as follows. 77 | /// 78 | /// RandomNumberGenerator randomObject = 79 | /// CryptographyProviders.RngCreator(); 80 | /// 81 | /// 82 | /// 83 | /// If you want to replace the implementation, you will likely do 84 | /// something like this in your application. 85 | /// 86 | /// CryptographyProviders.RngCreator = () => 87 | /// { 88 | /// Handle rngHandle = RngImpl.GetRngHandle(); 89 | /// return RngImpl.GetRandomObject(rngHandle); 90 | /// }; 91 | /// 92 | /// 93 | /// 94 | public static Func RngCreator { get; set; } = RandomNumberGenerator.Create; 95 | 96 | /// 97 | /// This property is a delegate (function pointer). The method loaded 98 | /// will return an instance of SHA1. 99 | /// 100 | /// 101 | /// When an SDK operation needs to digest data using SHA-1, it will do so 102 | /// using an implementation of the 103 | /// System.Security.Cryptography.SHA1 abstract class. However, 104 | /// when it needs the SHA1 class, it will ask this delegate to build an 105 | /// object, rather than ask for an object itself. This has to do with the 106 | /// fact that the SHA1 class implements IDisposable. In 107 | /// order to avoid the complex problem of ownership of an object that 108 | /// will be disposed once it goes out of scope, the SDK will create a new 109 | /// object each time one is needed. This new object will be in scope only 110 | /// for the duration of its use in the SDK, and will be disposed 111 | /// immediately when the SDK is done with it. 112 | /// 113 | /// The method loaded will return an object. This class is initialized 114 | /// with a method that will build and return an instance of the C# 115 | /// default implementation. For example, it could be used as follows. 116 | /// 117 | /// SHA1 sha1Object = CryptographyProviders.Sha1Creator(); 118 | /// 119 | /// 120 | /// 121 | /// If you want to replace the implementation, you will likely do 122 | /// something like this in your application. 123 | /// 124 | /// CryptographyProviders.Sha1Creator = () => 125 | /// { 126 | /// Handle sha1Handle = Sha1Impl.GetSha1Handle(); 127 | /// return Sha1Impl.GetSha1Object(sha1Handle); 128 | /// }; 129 | /// 130 | /// 131 | /// 132 | public static Func Sha1Creator { get; set; } = SHA1.Create; 133 | 134 | /// 135 | /// This property is a delegate (function pointer). The method loaded 136 | /// will return an instance of SHA256. 137 | /// 138 | /// 139 | /// When an SDK operation needs to digest data using SHA-256, it will do 140 | /// so using an implementation of the 141 | /// System.Security.Cryptography.SHA256 abstract class. However, 142 | /// when it needs the SHA256 class, it will ask this delegate to build an 143 | /// object, rather than ask for an object itself. This has to do with the 144 | /// fact that the SHA256 class implements IDisposable. In 145 | /// order to avoid the complex problem of ownership of an object that 146 | /// will be disposed once it goes out of scope, the SDK will create a new 147 | /// object each time one is needed. This new object will be in scope only 148 | /// for the duration of its use in the SDK, and will be disposed 149 | /// immediately when the SDK is done with it. 150 | /// 151 | /// The method loaded will return an object. This class is initialized 152 | /// with a method that will build and return an instance of the C# 153 | /// default implementation. For example, it could be used as follows. 154 | /// 155 | /// SHA256 sha256Object = CryptographyProviders.Sha256Creator(); 156 | /// 157 | /// 158 | /// 159 | /// If you want to replace the implementation, you will likely do 160 | /// something like this in your application. 161 | /// 162 | /// CryptographyProviders.Sha256Creator = () => 163 | /// { 164 | /// Handle sha256Handle = Sha256Impl.GetSha256Handle(); 165 | /// return Sha256Impl.GetSha256Object(sha256Handle); 166 | /// }; 167 | /// 168 | /// 169 | /// 170 | public static Func Sha256Creator { get; set; } = SHA256.Create; 171 | 172 | /// 173 | /// This property is a delegate (function pointer). The method loaded 174 | /// will return an instance of SHA384. 175 | /// 176 | /// 177 | /// When an SDK operation needs to digest data using SHA-384, it will do 178 | /// so using an implementation of the 179 | /// System.Security.Cryptography.SHA384 abstract class. However, 180 | /// when it needs the SHA384 class, it will ask this delegate to build an 181 | /// object, rather than ask for an object itself. This has to do with the 182 | /// fact that the SHA384 class implements IDisposable. In 183 | /// order to avoid the complex problem of ownership of an object that 184 | /// will be disposed once it goes out of scope, the SDK will create a new 185 | /// object each time one is needed. This new object will be in scope only 186 | /// for the duration of its use in the SDK, and will be disposed 187 | /// immediately when the SDK is done with it. 188 | /// 189 | /// The method loaded will return an object. This class is initialized 190 | /// with a method that will build and return an instance of the C# 191 | /// default implementation. For example, it could be used as follows. 192 | /// 193 | /// SHA384 sha384Object = CryptographyProviders.Sha384Creator(); 194 | /// 195 | /// 196 | /// 197 | /// If you want to replace the implementation, you will likely do 198 | /// something like this in your application. 199 | /// 200 | /// CryptographyProviders.Sha384Creator = () => 201 | /// { 202 | /// Handle sha384Handle = Sha384Impl.GetSha384Handle(); 203 | /// return Sha384Impl.GetSha384Object(sha384Handle); 204 | /// }; 205 | /// 206 | /// 207 | /// 208 | public static Func Sha384Creator { get; set; } = SHA384.Create; 209 | 210 | /// 211 | /// This property is a delegate (function pointer). The method loaded 212 | /// will return an instance of SHA512. 213 | /// 214 | /// 215 | /// When an SDK operation needs to digest data using SHA-512, it will do 216 | /// so using an implementation of the 217 | /// System.Security.Cryptography.SHA512 abstract class. However, 218 | /// when it needs the SHA512 class, it will ask this delegate to build an 219 | /// object, rather than ask for an object itself. This has to do with the 220 | /// fact that the SHA512 class implements IDisposable. In 221 | /// order to avoid the complex problem of ownership of an object that 222 | /// will be disposed once it goes out of scope, the SDK will create a new 223 | /// object each time one is needed. This new object will be in scope only 224 | /// for the duration of its use in the SDK, and will be disposed 225 | /// immediately when the SDK is done with it. 226 | /// 227 | /// The method loaded will return an object. This class is initialized 228 | /// with a method that will build and return an instance of the C# 229 | /// default implementation. For example, it could be used as follows. 230 | /// 231 | /// SHA512 sha512Object = CryptographyProviders.Sha512Creator(); 232 | /// 233 | /// 234 | /// 235 | /// If you want to replace the implementation, you will likely do 236 | /// something like this in your application. 237 | /// 238 | /// CryptographyProviders.Sha512Creator = () => 239 | /// { 240 | /// Handle sha512Handle = Sha512Impl.GetSha512Handle(); 241 | /// return Sha512Impl.GetSha512Object(sha512Handle); 242 | /// }; 243 | /// 244 | /// 245 | /// 246 | public static Func Sha512Creator { get; set; } = SHA512.Create; 247 | 248 | /// 249 | /// This property is a delegate (function pointer). The method loaded 250 | /// will return an instance of AES. 251 | /// 252 | /// 253 | /// When an SDK operation needs to encrypt or decrypt data using 254 | /// AES, it will do so using an implementation of the 255 | /// System.Security.Cryptography.AES abstract class. 256 | /// However, when it needs the AES class, it will ask this delegate to 257 | /// build an object, rather than ask for an object itself. This has to do 258 | /// with the fact that the AES class implements 259 | /// IDisposable. In order to avoid the complex problem of 260 | /// ownership of an object that will be disposed once it goes out of 261 | /// scope, the SDK will create a new object each time one is needed. This 262 | /// new object will be in scope only for the duration of its use in the 263 | /// SDK, and will be disposed immediately when the SDK is done with it. 264 | /// 265 | /// The method loaded will return an object. This class is initialized 266 | /// with a method that will build and return an instance of the C# 267 | /// default implementation. For example, it could be used as follows. 268 | /// 269 | /// AES aesObject = CryptographyProviders.AesCreator(); 270 | /// 271 | /// 272 | /// 273 | /// If you want to replace the implementation, you will likely do 274 | /// something like this in your application. 275 | /// 276 | /// CryptographyProviders.AesCreator = () => 277 | /// { 278 | /// Handle aesHandle = GetHandle(); 279 | /// return AesImpl.GetAesObject(aesHandle); 280 | /// }; 281 | /// 282 | /// 283 | /// 284 | public static Func AesCreator { get; set; } = Aes.Create; 285 | 286 | /// 287 | /// This property is a delegate (function pointer). The method loaded 288 | /// will return an instance of TripleDES. 289 | /// 290 | /// 291 | /// When an SDK operation needs to encrypt or decrypt data using 292 | /// Triple-DES, it will do so using an implementation of the 293 | /// System.Security.Cryptography.TripleDES abstract class. 294 | /// However, when it needs the Triple-DES class, it will ask this 295 | /// delegate to build an object, rather than ask for an object itself. 296 | /// This has to do with the fact that the TripleDES class 297 | /// implements IDisposable. In order to avoid the complex problem 298 | /// of ownership of an object that will be disposed once it goes out of 299 | /// scope, the SDK will create a new object each time one is needed. This 300 | /// new object will be in scope only for the duration of its use in the 301 | /// SDK, and will be disposed immediately when the SDK is done with it. 302 | /// 303 | /// The method loaded will return an object. This class is initialized 304 | /// with a method that will build and return an instance of the C# 305 | /// default implementation. For example, it could be used as follows. 306 | /// 307 | /// TripleDES tripleDesObject = CryptographyProviders.TripleDesCreator(); 308 | /// 309 | /// 310 | /// 311 | /// If you want to replace the implementation, you will likely do 312 | /// something like this in your application. 313 | /// 314 | /// CryptographyProviders.TripleDesCreator = () => 315 | /// { 316 | /// Handle tripleDesHandle = GetHandle(); 317 | /// return TDesImpl.GetTripleDesObject(tripleDesHandle); 318 | /// }; 319 | /// 320 | /// 321 | /// 322 | #pragma warning disable CA5350 // The PIV standard requires Triple-DES. 323 | public static Func TripleDesCreator { get; set; } = TripleDES.Create; 324 | #pragma warning restore CA5350 325 | 326 | /// 327 | /// This property is a delegate (function pointer). The method loaded 328 | /// will return an instance of DES. 329 | /// 330 | /// 331 | /// When an SDK operation needs to encrypt or decrypt data using 332 | /// DES, it will do so using an implementation of the 333 | /// System.Security.Cryptography.DES abstract class. 334 | /// However, when it needs the DES class, it will ask this 335 | /// delegate to build an object, rather than ask for an object itself. 336 | /// This has to do with the fact that the DES class 337 | /// implements IDisposable. In order to avoid the complex problem 338 | /// of ownership of an object that will be disposed once it goes out of 339 | /// scope, the SDK will create a new object each time one is needed. This 340 | /// new object will be in scope only for the duration of its use in the 341 | /// SDK, and will be disposed immediately when the SDK is done with it. 342 | /// 343 | /// The method loaded will return an object. This class is initialized 344 | /// with a method that will build and return an instance of the C# 345 | /// default implementation. For example, it could be used as follows. 346 | /// 347 | /// DES desObject = CryptographyProviders.DesCreator(); 348 | /// 349 | /// 350 | /// 351 | /// If you want to replace the implementation, you will likely do 352 | /// something like this in your application. 353 | /// 354 | /// CryptographyProviders.DesCreator = () => 355 | /// { 356 | /// Handle desHandle = GetHandle(); 357 | /// return DesImpl.GetDesObject(desHandle); 358 | /// }; 359 | /// 360 | /// 361 | /// 362 | public static Func DesCreator { get; set; } = DES.Create; 363 | } 364 | } 365 | -------------------------------------------------------------------------------- /Yubikey/Cryptography/RandomNumberGeneratorExt.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Yubico AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Portions of this file have been adopted from the .NET runtime under the following license: 16 | // Licensed to the .NET Foundation under one or more agreements. 17 | // The .NET Foundation licenses this file to you under the MIT license. 18 | 19 | // Original source: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RandomNumberGenerator.cs 20 | 21 | using System; 22 | using System.Globalization; 23 | using System.Security.Cryptography; 24 | 25 | namespace Yubico.YubiKey.Cryptography 26 | { 27 | /// 28 | /// Extension class to extend random number functionality. 29 | /// 30 | public static class RandomNumberGeneratorExt 31 | { 32 | /// 33 | /// Gets a random 32-bit signed int. 34 | /// 35 | /// The instance being extended. 36 | /// The lowest value of the range. 37 | /// One above the highest value of the range. 38 | /// Random . 39 | public static int GetInt32( 40 | this RandomNumberGenerator rng, 41 | int fromInclusive, 42 | int toExclusive) 43 | { 44 | if (rng is null) 45 | { 46 | throw new ArgumentNullException(nameof(rng)); 47 | } 48 | if (fromInclusive >= toExclusive) 49 | { 50 | throw new ArithmeticException(string.Format( 51 | CultureInfo.CurrentCulture, 52 | ExceptionMessages.ValueMustBeBetweenXandY, 53 | int.MinValue, 54 | (long)int.MaxValue + 1)); 55 | } 56 | 57 | uint range = (uint)toExclusive - (uint)fromInclusive - 1; 58 | // Mask away bits beyond our range. 59 | uint mask = range; 60 | mask |= mask >> 1; 61 | mask |= mask >> 2; 62 | mask |= mask >> 4; 63 | mask |= mask >> 8; 64 | mask |= mask >> 16; 65 | uint result = int.MaxValue; 66 | while (result > range) 67 | { 68 | byte[] data = new byte[sizeof(int)]; 69 | rng.GetBytes(data); 70 | result = mask & BitConverter.ToUInt32(data, 0); 71 | } 72 | return (int)result + fromInclusive; 73 | } 74 | 75 | /// 76 | /// Fill a range with random bytes. 77 | /// 78 | /// The instance being extended. 79 | /// A to fill with random bytes. 80 | public static void Fill( 81 | this RandomNumberGenerator rng, 82 | Span data) 83 | { 84 | for (int i = 0; i < data.Length; ++i) 85 | { 86 | data[i] = rng.GetByte(0x00, 0x100); 87 | } 88 | } 89 | 90 | /// 91 | /// Get a with a random value. 92 | /// 93 | /// The instance being extended. 94 | /// The lowest value of the range. 95 | /// One above the highest value of the range. 96 | /// 97 | public static byte GetByte( 98 | this RandomNumberGenerator rng, 99 | int fromInclusive, 100 | int toExclusive) 101 | { 102 | if (fromInclusive < 0 103 | || toExclusive > 0x100 104 | || fromInclusive >= toExclusive) 105 | { 106 | throw new ArithmeticException(string.Format( 107 | CultureInfo.CurrentCulture, 108 | ExceptionMessages.ValueMustBeBetweenXandY, 109 | byte.MinValue, 110 | byte.MaxValue + 1)); 111 | } 112 | return (byte)rng.GetInt32(fromInclusive, toExclusive); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Yubikey/Iso7816/AnswerToReset.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Yubico AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Linq; 17 | 18 | namespace Yubico.Core.Iso7816 19 | { 20 | public class AnswerToReset 21 | { 22 | private readonly byte[] _bytes; 23 | 24 | public AnswerToReset(ReadOnlySpan bytes) 25 | { 26 | _bytes = bytes.ToArray(); 27 | } 28 | 29 | public override bool Equals(object? obj) => obj switch 30 | { 31 | AnswerToReset atr => this == atr, 32 | _ => false 33 | }; 34 | 35 | public override int GetHashCode() => _bytes.GetHashCode(); 36 | 37 | public override string ToString() => BitConverter.ToString(_bytes.ToArray()); 38 | 39 | public static bool operator ==(AnswerToReset l, AnswerToReset r) => (l, r) switch 40 | { 41 | (AnswerToReset _, null) => false, 42 | (null, AnswerToReset _) => false, 43 | (AnswerToReset left, AnswerToReset right) => left._bytes.AsSpan().SequenceEqual(right._bytes.AsSpan()), 44 | _ => false 45 | }; 46 | 47 | public static bool operator !=(AnswerToReset l, AnswerToReset r) => !(l == r); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Yubikey/Iso7816/ApduEncoding.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Yubico AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace Yubico.Core.Iso7816 16 | { 17 | /// 18 | /// Represents encoding options for an APDU's length fields. 19 | /// 20 | public enum ApduEncoding 21 | { 22 | /// 23 | /// Automatically determine the encoding length. 24 | /// 25 | Automatic = 0, 26 | 27 | /// 28 | /// Use short encoding. 29 | /// 30 | ShortLength = 1, 31 | 32 | /// 33 | /// Use extended encoding. 34 | /// 35 | ExtendedLength = 2 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Yubikey/Iso7816/ApduException.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Yubico AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Runtime.Serialization; 17 | 18 | namespace Yubico.Core.Iso7816 19 | { 20 | /// 21 | /// The exception that is thrown when an ISO 7816 application has encountered an error. 22 | /// 23 | [Serializable] 24 | public class ApduException : Exception 25 | { 26 | /// 27 | /// Gets or sets the status word (SW), the ISO 7816 numerical value which represents 28 | /// the specific error or warning encountered. 29 | /// 30 | /// 31 | /// The status word value. This can either be an industry defined error, or vendor defined. 32 | /// 33 | public short? SW { get; set; } 34 | 35 | /// 36 | /// Gets or sets the APDU class associated with this exception. 37 | /// 38 | /// 39 | /// The class value of the command APDU when the exception occurred. 40 | /// 41 | public byte? Cla { get; set; } 42 | 43 | /// 44 | /// Gets or sets the APDU instruction associated with this exception. 45 | /// 46 | /// 47 | /// The instruction of the command APDU when the exception occurred. 48 | /// 49 | public byte? Ins { get; set; } 50 | 51 | /// 52 | /// Gets or sets the P1 parameter associated with this exception. 53 | /// 54 | /// 55 | /// The first parameter of the command APDU when the exception occurred. 56 | /// 57 | public byte? P1 { get; set; } 58 | 59 | /// 60 | /// Gets or sets the P2 parameter associated with this exception. 61 | /// 62 | /// 63 | /// The second parameter of the command APDU when the exception occurred. 64 | /// 65 | public byte? P2 { get; set; } 66 | 67 | /// 68 | /// Initializes a new instance of the class with a default message. 69 | /// 70 | public ApduException() : 71 | base(ExceptionMessages.UnknownApduError) 72 | { 73 | 74 | } 75 | 76 | /// 77 | /// Initializes a new instance of the class with a specified error 78 | /// message. 79 | /// 80 | /// The message that describes the error. 81 | public ApduException(string message) : 82 | base(message) 83 | { 84 | 85 | } 86 | 87 | /// 88 | /// Initializes a new instance of the class with a specified error 89 | /// message and a reference to the inner exception that is the cause of this exception. 90 | /// 91 | /// The error message that explains the reason for the exception. 92 | /// The exception that is the cause of the current exception, 93 | /// or a null reference ( in Visual Basic) if no inner exception 94 | /// is specified. 95 | public ApduException(string message, Exception innerException) : 96 | base(message, innerException) 97 | { 98 | 99 | } 100 | 101 | /// 102 | protected ApduException(SerializationInfo serializationInfo, StreamingContext streamingContext) : 103 | base(serializationInfo, streamingContext) 104 | { 105 | 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Yubikey/Iso7816/CommandApdu.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Yubico AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Buffers.Binary; 17 | using System.Collections.Generic; 18 | using System.Globalization; 19 | using System.IO; 20 | using System.Linq; 21 | 22 | namespace Yubico.Core.Iso7816 23 | { 24 | /// 25 | /// Represents an ISO 7816 application command 26 | /// 27 | public class CommandApdu 28 | { 29 | private const int maximumSizeShortEncoding = 256; 30 | private const int maximumSizeExtendedEncoding = 65536; 31 | 32 | // Backing store for `public int Ne` 33 | private int _ne; 34 | 35 | /// 36 | /// Indicates the class of the instruction. 37 | /// 38 | public byte Cla { get; set; } 39 | 40 | /// 41 | /// Indicates the command or instruction to process. 42 | /// 43 | public byte Ins { get; set; } 44 | 45 | /// 46 | /// First parameter byte. 47 | /// 48 | public byte P1 { get; set; } 49 | 50 | /// 51 | /// Second parameter byte. 52 | /// 53 | public byte P2 { get; set; } 54 | 55 | /// 56 | /// Gets or sets the optional command data payload. 57 | /// 58 | public ReadOnlyMemory Data { get; set; } = ReadOnlyMemory.Empty; 59 | 60 | /// 61 | /// The number of bytes in . 62 | /// 63 | /// 64 | /// If is null, returns 0. 65 | /// 66 | public int Nc => Data.Length; 67 | 68 | /// 69 | /// The maximum number of bytes expected in the response data. 70 | /// Must be a non-negative number. 71 | /// 72 | /// 73 | /// Values of note: 74 | /// 75 | /// 76 | /// 0 77 | /// No data is expected to be returned. 78 | /// 79 | /// 80 | /// 81 | /// 82 | /// Maximum value according to the encoding used. See 83 | /// and . 84 | /// 85 | /// 86 | /// 87 | /// 88 | public int Ne 89 | { 90 | get => _ne; 91 | 92 | set 93 | { 94 | if (value < 0) 95 | { 96 | throw new ArgumentOutOfRangeException(nameof(Ne),ExceptionMessages.CommandApduNeRangeError); 97 | } 98 | else 99 | { 100 | _ne = value; 101 | } 102 | } 103 | } 104 | 105 | /// 106 | /// Initializes a new instance of the class. 107 | /// 108 | public CommandApdu() 109 | { 110 | 111 | } 112 | 113 | /// 114 | /// Transforms the CommandApdu into an array of bytes. 115 | /// 116 | /// 117 | /// Automatically determines the appropriate encoding to use. 118 | /// See also . 119 | /// 120 | /// An array of bytes representing an ISO 7816 CommandApdu. 121 | /// 122 | /// Thrown when no valid scheme is found for the 123 | /// current state of . 124 | /// 125 | public byte[] AsByteArray() => AsByteArray(ApduEncoding.Automatic); 126 | 127 | /// 128 | /// Transforms the CommandApdu into an array of bytes. 129 | /// 130 | /// 131 | /// All fields must be valid for the given 132 | /// . 133 | /// 134 | /// 135 | /// The in which the output is written. 136 | /// 137 | /// An array of bytes representing an ISO 7816 CommandApdu. 138 | /// 139 | /// Thrown when no valid scheme is found for the 140 | /// current state of . 141 | /// 142 | /// 143 | /// Thrown when is invalid. 144 | /// 145 | public byte[] AsByteArray(ApduEncoding apduEncoding) 146 | { 147 | if (apduEncoding == ApduEncoding.Automatic) 148 | { 149 | apduEncoding = GetApduEncoding(); 150 | } 151 | 152 | using var apduBuffer = new MemoryStream(); 153 | using var apduWriter = new BinaryWriter(apduBuffer); 154 | 155 | // Write command header 156 | apduWriter.Write(Cla); 157 | apduWriter.Write(Ins); 158 | apduWriter.Write(P1); 159 | apduWriter.Write(P2); 160 | 161 | // Write Lc 162 | apduWriter.Write(GetLcField(apduEncoding)); 163 | 164 | // Write Data 165 | apduWriter.Write(Data.ToArray()); 166 | 167 | // Write Le 168 | apduWriter.Write(GetLeField(apduEncoding)); 169 | 170 | return apduBuffer.ToArray(); 171 | } 172 | 173 | // Uses the current values of Nc and Ne to determine the appropriate 174 | // ApduEncoding to use. If there is no valid encoding, it throws an exception. 175 | private ApduEncoding GetApduEncoding() 176 | { 177 | if (ValidNc(ApduEncoding.ShortLength) && ValidNe(ApduEncoding.ShortLength)) 178 | { 179 | return ApduEncoding.ShortLength; 180 | } 181 | else if (ValidNc(ApduEncoding.ExtendedLength) && ValidNe(ApduEncoding.ExtendedLength)) 182 | { 183 | return ApduEncoding.ExtendedLength; 184 | } 185 | else 186 | { 187 | throw new InvalidOperationException(ExceptionMessages.CommandApduNoValidEncoding); 188 | } 189 | } 190 | 191 | // Returns the inclusive upper bound for a data length value. 192 | // 193 | // is not supported. 194 | // 195 | private static int GetInclusiveUpperBound(ApduEncoding apduEncoding) => 196 | apduEncoding switch 197 | { 198 | ApduEncoding.Automatic => maximumSizeExtendedEncoding, 199 | ApduEncoding.ShortLength => maximumSizeShortEncoding, 200 | ApduEncoding.ExtendedLength => maximumSizeExtendedEncoding, 201 | _ => throw new ArgumentOutOfRangeException(nameof(apduEncoding)), 202 | }; 203 | 204 | // Checks that Nc is valid, given the encoding. 205 | private bool ValidNc(ApduEncoding apduEncoding) 206 | { 207 | int inclusiveUpperBound = GetInclusiveUpperBound(apduEncoding); 208 | 209 | return Nc >= 0 && Nc <= inclusiveUpperBound; 210 | } 211 | 212 | // Checks that Ne is valid, given the encoding. 213 | private bool ValidNe(ApduEncoding apduEncoding) 214 | { 215 | int inclusiveUpperBound = GetInclusiveUpperBound(apduEncoding); 216 | 217 | return (Ne == int.MaxValue) || (Ne >= 0 && Ne <= inclusiveUpperBound); 218 | } 219 | 220 | // Validates Nc, then returns the Lc field as a byte array in the given encoding. 221 | // Does not accept ApduEncoding.Automatic; see CommandApdu.GetApduEncoding(). 222 | private byte[] GetLcField(ApduEncoding apduEncoding) 223 | { 224 | if (apduEncoding == ApduEncoding.Automatic) 225 | { 226 | throw new ArgumentOutOfRangeException(nameof(apduEncoding)); 227 | } 228 | else 229 | { 230 | if (!ValidNc(apduEncoding)) 231 | { 232 | throw new InvalidOperationException( 233 | string.Format( 234 | CultureInfo.CurrentCulture, 235 | ExceptionMessages.CommandApduFieldOutOfRangeEncoding, 236 | nameof(Nc), 237 | Enum.GetName(typeof(ApduEncoding), apduEncoding))); 238 | } 239 | } 240 | 241 | byte[] lcField = Array.Empty(); 242 | 243 | if (Nc > 0) 244 | { 245 | int lcValue = Nc; // The encoded value, derived from Nc 246 | 247 | if (apduEncoding == ApduEncoding.ExtendedLength) 248 | { 249 | if (Nc == maximumSizeExtendedEncoding) 250 | { 251 | lcValue = 0; 252 | } 253 | 254 | lcField = new byte[3]; 255 | lcField[0] = 0; 256 | BinaryPrimitives.WriteInt16BigEndian(lcField.AsSpan(1), (short)lcValue); 257 | } 258 | else 259 | { 260 | if (Nc == maximumSizeShortEncoding) 261 | { 262 | lcValue = 0; 263 | } 264 | 265 | lcField = new byte[] { (byte)lcValue }; 266 | } 267 | } 268 | 269 | return lcField; 270 | } 271 | 272 | // Validates Ne, then writes the Le field as a byte array in the given encoding. 273 | // Does not accept ApduEncoding.Automatic; see CommandApdu.GetApduEncoding(). 274 | private byte[] GetLeField(ApduEncoding apduEncoding) 275 | { 276 | if (apduEncoding == ApduEncoding.Automatic) 277 | { 278 | throw new ArgumentOutOfRangeException(nameof(apduEncoding)); 279 | } 280 | else 281 | { 282 | if (!ValidNe(apduEncoding)) 283 | { 284 | throw new InvalidOperationException( 285 | string.Format( 286 | CultureInfo.CurrentCulture, 287 | ExceptionMessages.CommandApduFieldOutOfRangeEncoding, 288 | nameof(Ne), 289 | Enum.GetName(typeof(ApduEncoding), apduEncoding))); 290 | } 291 | } 292 | 293 | byte[] leField = Array.Empty(); 294 | 295 | if (Ne > 0) 296 | { 297 | int leValue = Ne == int.MaxValue ? 0 : Ne; // The encoded value, derived from Ne 298 | 299 | if (apduEncoding == ApduEncoding.ExtendedLength) 300 | { 301 | if (Ne == maximumSizeExtendedEncoding) 302 | { 303 | leValue = 0; 304 | } 305 | 306 | if (Nc == 0) 307 | { 308 | leField = new byte[3]; 309 | leField[0] = 0; 310 | BinaryPrimitives.WriteInt16BigEndian(leField.AsSpan(1), (short)leValue); 311 | } 312 | else 313 | { 314 | leField = new byte[2]; 315 | BinaryPrimitives.WriteInt16BigEndian(leField, (short)leValue); 316 | } 317 | } 318 | else 319 | { 320 | 321 | if (Ne == maximumSizeShortEncoding) 322 | { 323 | leValue = 0; 324 | } 325 | 326 | leField = new byte[] { (byte)leValue }; 327 | } 328 | } 329 | 330 | return leField; 331 | } 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /Yubikey/Iso7816/ResponseApdu.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Yubico AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Globalization; 17 | using System.Linq; 18 | 19 | namespace Yubico.Core.Iso7816 { 20 | /// 21 | /// Represents an ISO 7816 application response. 22 | /// 23 | public class ResponseApdu 24 | { 25 | /// 26 | /// The status word (two byte) code which represents the overall result of a CCID interaction. 27 | /// The most common value is 0x9000 which represents a successful result. 28 | /// 29 | public short SW => (short)((SW1 << 8) | SW2); 30 | 31 | /// 32 | /// A convenience property accessor for the high byte of SW 33 | /// 34 | public byte SW1 { get; private set; } 35 | 36 | /// 37 | /// A convenience property accessor for the low byte of SW 38 | /// 39 | public byte SW2 { get; private set; } 40 | 41 | /// 42 | /// Gets the data part of the response. 43 | /// 44 | /// 45 | /// The raw bytes not including the ending status word. 46 | /// 47 | public ReadOnlyMemory Data { get; private set; } 48 | 49 | /// 50 | /// Initializes a new instance of the class. 51 | /// 52 | /// The raw data returned by the ISO 7816 smart card. 53 | public ResponseApdu(byte[] data) 54 | { 55 | if (data is null) 56 | { 57 | throw new ArgumentNullException(nameof(data)); 58 | } 59 | 60 | if (data.Length < 2) 61 | { 62 | throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionMessages.ResponseApduNotEnoughBytes, data.Length)); 63 | } 64 | 65 | SW1 = data[data.Length-2]; 66 | SW2 = data[data.Length-1]; 67 | Data = data.Take(data.Length - 2).ToArray(); 68 | } 69 | 70 | /// 71 | /// Initializes a new instance of the class. 72 | /// 73 | /// The raw data returned by the ISO 7816 smart card without the 74 | /// trailing status bytes. 75 | /// The status word, 'SW', for the APDU response. 76 | public ResponseApdu(byte[] dataWithoutSW, short sw) 77 | { 78 | if (dataWithoutSW is null) 79 | { 80 | throw new ArgumentNullException(nameof(dataWithoutSW)); 81 | } 82 | 83 | SW1 = (byte)(sw >> 8); 84 | SW2 = (byte)(sw & 0xFF); 85 | Data = dataWithoutSW.ToArray(); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Yubikey/Iso7816/SW1Constants.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Yubico AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace Yubico.Core.Iso7816 16 | { 17 | public static class SW1Constants 18 | { 19 | // Normal processing 20 | public const byte Success = 0x90; 21 | public const byte BytesAvailable = 0x61; 22 | 23 | // Warning processing 24 | public const byte WarningNvmUnchanged = 0x62; 25 | public const byte WarningNvmChanged = 0x63; 26 | 27 | // Execution error 28 | public const byte ExecutionErrorNvmUnchanged = 0x64; 29 | public const byte ExecutionErrorNvmChanged = 0x65; 30 | public const byte SecurityError = 0x66; 31 | 32 | // Checking error 33 | public const byte WrongLength = 0x67; 34 | public const byte FunctionNotSupported = 0x68; 35 | public const byte CommandNotAllowed = 0x69; 36 | public const byte WrongParametersQualified = 0x6A; 37 | public const byte WrongParametersUnqualified = 0x6B; 38 | public const byte WrongLengthField = 0x6C; 39 | public const byte InstructionInvalid = 0x6D; 40 | public const byte ClassNotSupported = 0x6E; 41 | public const byte NoPreciseDiagnosis = 0x6F; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Yubikey/Iso7816/SWConstants.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Yubico AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace Yubico.Core.Iso7816 16 | { 17 | public static class SWConstants 18 | { 19 | // Success 20 | public const short Success = unchecked((short)0x9000); 21 | 22 | // Warning 23 | public const short WarningNvmUnchanged = 0x6200; 24 | public const short PartialCorruption = 0x6281; 25 | public const short EOFReached = 0x6282; 26 | public const short FileDeactivated = 0x6283; 27 | public const short InvalidFileFormat = 0x6284; 28 | public const short FileTerminated = 0x6285; 29 | public const short NoSensorData = 0x6286; 30 | 31 | public const short WarningNvmChanged = 0x6300; 32 | public const short NoMoreSpaceInFile = 0x6381; 33 | public const short VerifyFail = 0x63C0; 34 | 35 | // Error 36 | public const short ExecutionError = 0x6400; 37 | public const short ResponseRequired = 0x6401; 38 | 39 | public const short ErrorNvmChanged = 0x6500; 40 | public const short MemoryFailure = 0x6581; 41 | 42 | public const short WrongLength = 0x6700; 43 | 44 | public const short FunctionError = 0x6800; 45 | public const short LogicalChannelNotSupported = 0x6881; 46 | public const short SecureMessagingNotSupported = 0x6882; 47 | public const short LastCommandOfChainExpected = 0x6883; 48 | public const short CommandChainingNotSupported = 0x6884; 49 | 50 | public const short CommandNotAllowed = 0x6900; 51 | public const short IncompatibleCommand = 0x6981; 52 | public const short SecurityStatusNotSatisfied = 0x6982; 53 | public const short AuthenticationMethodBlocked = 0x6983; 54 | public const short ReferenceDataUnusable = 0x6984; 55 | public const short ConditionsNotSatisfied = 0x6985; 56 | public const short CommandNotAllowedNoEF = 0x6986; 57 | public const short SecureMessageDataMissing = 0x6987; 58 | public const short SecureMessageMalformed = 0x6988; 59 | 60 | public const short InvalidParameter = 0x6A00; 61 | public const short InvalidCommandDataParameter = 0x6A80; 62 | public const short FunctionNotSupported = 0x6A81; 63 | public const short FileOrApplicationNotFound = 0x6A82; 64 | public const short RecordNotFound = 0x6A83; 65 | public const short NotEnoughSpace = 0x6A84; 66 | public const short InconsistentLengthWithTlv = 0x6A85; 67 | public const short IncorrectP1orP2 = 0x6A86; 68 | public const short InconsistentLengthWithP1P2 = 0x6A87; 69 | public const short DataNotFound = 0x6A88; 70 | public const short FileAlreadyExists = 0x6A89; 71 | public const short DFNameAlreadyExists = 0x6A8A; 72 | 73 | public const short InsNotSupported = 0x6D00; 74 | 75 | public const short ClaNotSupported = 0x6E00; 76 | 77 | public const short NoPreciseDiagnosis = 0x6F00; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Yubikey/Logging/Log.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Yubico AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace Yubico.Core.Logging 16 | { 17 | /// 18 | /// A static class for managing Yubico SDK logging for this process. 19 | /// 20 | /// 21 | /// 22 | /// This class is used for managing the active logger used globally by .NET-based Yubico SDKs in the current process. 23 | /// Changing the settings in this class will not have any effect on applications or services that are not running 24 | /// within the current application's process. It will affect all libraries contained within - for example, changing 25 | /// the logger factory here will impact both the Yubico.YubiKey and Yubico.Core libraries. 26 | /// 27 | /// 28 | /// The property is used to set and control the concrete log to be used by the SDK. By 29 | /// default, we send logs to the "null" logger - effectively disabling logging. If you set this property with your 30 | /// own logger factory, the SDK will use this log from the point of the set until someone calls this set method again. 31 | /// 32 | /// 33 | /// should be used to return an instance of the class. This is the object 34 | /// used to actually write the log messages. It is generally OK to cache an instance of a logger within another 35 | /// class instance. Holding a Logger instance open longer than that is not recommended, as changes to the LoggerFactory 36 | /// will not be reflected until you call the `GetLogger` method again. 37 | /// 38 | /// 39 | public static class Log 40 | { 41 | /// 42 | /// Gets an instance of the active logger. 43 | /// 44 | /// 45 | /// An instance of the active concrete logger. 46 | /// 47 | /// 48 | /// 49 | /// Write some information to the log. 50 | /// 51 | /// 52 | /// using Yubico.Core.Logging; 53 | /// 54 | /// public class Example 55 | /// { 56 | /// private Logger _log = Log.GetLogger(); 57 | /// 58 | /// public void SampleMethod() 59 | /// { 60 | /// _log.LogDebug("The SampleMethod method has been called!"); 61 | /// } 62 | /// 63 | /// public void StaticMethod() 64 | /// { 65 | /// Logger log = Log.GetLogger(); // Can't use the instance logger because we're static. 66 | /// log.LogDebug("Called from a static method!"); 67 | /// } 68 | /// } 69 | /// 70 | /// 71 | public static Logger GetLogger() => new Logger(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Yubikey/Logging/Logger.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Yubico AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | 17 | namespace Yubico.Core.Logging 18 | { 19 | /// 20 | /// A concrete logger implementation used by Yubico .NET-based libraries. 21 | /// 22 | /// 23 | /// 24 | /// This class builds on top of the standard interface used by the Microsoft.Extensions logging 25 | /// library. This is a meta-library for interoperating with different concrete logging implementations such as NLog, 26 | /// Serilog, or .NET's built in EventPipe system. 27 | /// 28 | /// 29 | /// Methods for logging potentially sensitive information are present. These methods are disabled for Release builds, 30 | /// resulting in a no-op for anything other than a Debug build of this library. 31 | /// 32 | /// 33 | /// Extension methods can be used to add further conveniences to the logging interface. For example, if you wanted to 34 | /// log a platform error code in a uniform way, you could introduce a `LogPlatformError` extension that takes care of 35 | /// formatting the error and calling one of the existing log methods. 36 | /// 37 | /// 38 | public sealed class Logger { 39 | 40 | internal Logger() 41 | { 42 | 43 | } 44 | 45 | internal void LogInformation(string v) { 46 | 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Yubikey/Objects/CardCapabilityContainer.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Yubico AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Security.Cryptography; 17 | using System.Globalization; 18 | using Yubico.YubiKey.Cryptography; 19 | using Yubico.Core.Tlv; 20 | using Yubico.Core.Logging; 21 | 22 | namespace Yubico.YubiKey.Piv.Objects 23 | { 24 | /// 25 | /// Use this class to process the CCC (Card Capability Container) data. 26 | /// 27 | /// 28 | /// The PIV standard declares, 29 | /// 30 | /// "The Card Capability Container (CCC) is a mandatory data object whose 31 | /// purpose is to facilitate compatibility of Government Smart Card 32 | /// Interoperability Specification (GSC-IS) applications with PIV Cards." 33 | /// 34 | /// 35 | /// In other words, it's a holdover from the older smart card specification. 36 | /// In order to remain compatible with that older spec and with older 37 | /// applications, it might be necessary to read and write this data object. 38 | /// 39 | /// 40 | /// There are many elements that make up the CCC, but most of them are 41 | /// ignored by PIV and the YubiKey. Other elements are fixed. Note that the 42 | /// PIV standard says, 43 | /// 44 | /// 45 | /// "The data model of the PIV Card Application shall be identified by 46 | /// data model number 0x10. ... The content of the CCC data elements, other 47 | /// than the data model number, are out of scope for this specification." 48 | /// 49 | /// 50 | /// There is only one element that can be set in this class, namely, the Card 51 | /// Identifier portion of the Unique Card Identifier. This is a 14-byte 52 | /// value. With the YubiKey, the caller sets it, or allows the SDK to set it 53 | /// to random bytes. 54 | /// 55 | /// 56 | /// Upon manufacture, the CCC is "empty", so the 57 | /// property is true. This object will 58 | /// be considered empty until the Card Identifier is set. See 59 | /// and . 60 | /// 61 | /// 62 | /// The following list indicates the elements of the CCC that can be found on 63 | /// a YubiKey. 64 | /// 65 | /// Unique Card Identifier 66 | /// Application Identifier (part of the Unique Card ID 67 | /// GSC-RID (Registered Application Provider Identifier, 68 | /// part of the AID) 69 | /// Card Identifier (part of the Unique Card ID) 70 | /// Manufacturer ID 71 | /// Card Type 72 | /// Container Version Number 73 | /// Grammar Version Number 74 | /// PKCS #15 Version Number (for the YubiKey, this is 0x00 75 | /// indicating PKCS #15 is not supported 76 | /// Data Model Number 77 | /// 78 | /// 79 | /// 80 | public sealed class CardCapabilityContainer : PivDataObject 81 | { 82 | private const int CccDefinedDataTag = 0x005FC107; 83 | private const int AidOffset = 0; 84 | private const int AidLength = 7; 85 | private const int GscRidOffset = 0; 86 | private const int GscRidLength = 5; 87 | private const int CardIdOffset = 7; 88 | private const int CardIdLength = 14; 89 | private const int FixedManufacturerId = 0xFF; 90 | private const int CardTypeJavaCard = 0x02; 91 | private const byte FixedContainerVersionNumber = 0x21; 92 | private const byte FixedGrammarVersionNumber = 0x21; 93 | private const byte FixedPkcs15VersionNumber = 0x00; 94 | private const byte FixedDataModelNumber = 0x10; 95 | private const int EncodingTag = 0x53; 96 | private const int UniqueCardIdTag = 0xF0; 97 | private const int UniqueCardIdLength = 0x15; 98 | private const int ContainerVersionTag = 0xF1; 99 | private const int GrammarVersionTag = 0xF2; 100 | private const int UnusedTag1 = 0xF3; 101 | private const int Pkcs15Tag = 0xF4; 102 | private const int DataModelTag = 0xF5; 103 | private const int UnusedTag2 = 0xF6; 104 | private const int UnusedTag3 = 0xF7; 105 | private const int UnusedTag4 = 0xFA; 106 | private const int UnusedTag5 = 0xFB; 107 | private const int UnusedTag6 = 0xFC; 108 | private const int UnusedTag7 = 0xFD; 109 | private const int UnusedTag8 = 0xFE; 110 | 111 | private bool _disposed; 112 | private readonly Logger _log = Log.GetLogger(); 113 | 114 | /// 115 | /// The full Unique Card Identifier which consists of the AID || CardID. 116 | /// 117 | public ReadOnlyMemory UniqueCardIdentifier { get; private set; } 118 | 119 | private readonly byte[] _uniqueCardIdentifier = new byte[] { 120 | 0xA0, 0x00, 0x00, 0x01, 0x16, 0xFF, 0x02, 121 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 122 | }; 123 | 124 | /// 125 | /// The "AID" (Capabilities Application Identifier), which consists of 126 | /// the GSC-RID || ManufacturerID || CardType. 127 | /// 128 | public ReadOnlyMemory ApplicationIdentifier { get; private set; } 129 | 130 | /// 131 | /// The "Government Smart Card - Registered Application Provider 132 | /// Identifier". 133 | /// 134 | public ReadOnlyMemory GscRid { get; private set; } 135 | 136 | /// 137 | /// The actual Card Identifier portion of the Unique Card Identifier. 138 | /// 139 | public ReadOnlyMemory CardIdentifier { get; private set; } 140 | 141 | /// 142 | /// The manufacturer ID is fixed at 0xFF 143 | /// 144 | public int ManufacturerId => FixedManufacturerId; 145 | 146 | /// 147 | /// The card type is fixed at JavaCard. 148 | /// 149 | public int CardType => CardTypeJavaCard; 150 | 151 | /// 152 | /// The version number of the CCC itself, it is fixed at version 2.1. 153 | /// 154 | public byte ContainerVersionNumber => FixedContainerVersionNumber; 155 | 156 | /// 157 | /// The version number of the CCC grammar, it is fixed at version 2.1. 158 | /// 159 | public byte GrammarVersionNumber => FixedGrammarVersionNumber; 160 | 161 | /// 162 | /// The version of PKCS #15 the card supports. If the card does not 163 | /// support PKCS #15, this number is 0x00. For the YubiKey it is fixed at 164 | /// 0x00. 165 | /// 166 | public byte Pkcs15Version => FixedPkcs15VersionNumber; 167 | 168 | /// 169 | /// The number representing the Data Model used by the smart card. For 170 | /// the YubiKey it is fixed at 0x10 (a PIV requirement). 171 | /// 172 | public byte DataModelNumber => FixedDataModelNumber; 173 | 174 | /// 175 | /// Build a new object. This will not get the CCC from from any YubiKey, 176 | /// it will only build an "empty" object. 177 | /// 178 | /// 179 | /// To read the CCC data out of a YubiKey, call a 180 | /// method. 181 | /// 182 | public CardCapabilityContainer() 183 | { 184 | _log.LogInformation("Create a new instance of CardCapabilityContainer."); 185 | _disposed = false; 186 | DataTag = CccDefinedDataTag; 187 | 188 | IsEmpty = true; 189 | UniqueCardIdentifier = new ReadOnlyMemory(_uniqueCardIdentifier); 190 | ApplicationIdentifier = UniqueCardIdentifier.Slice(AidOffset, AidLength); 191 | GscRid = UniqueCardIdentifier.Slice(GscRidOffset, GscRidLength); 192 | CardIdentifier = UniqueCardIdentifier.Slice(CardIdOffset, CardIdLength); 193 | } 194 | 195 | /// 196 | public override int GetDefinedDataTag() => CccDefinedDataTag; 197 | 198 | /// 199 | /// Set the CardId with a random, 14-byte value. 200 | /// 201 | /// 202 | /// This method will use the random number generator built by 203 | /// to generate 14 random bytes as 204 | /// the new CardId. 205 | /// 206 | /// If there is a CardId value already in this object, this method will 207 | /// overwrite it. 208 | /// 209 | /// 210 | public void SetRandomCardId() 211 | { 212 | _log.LogInformation("Set the CardId of CardCapabilityContainer with a random value."); 213 | Clear(); 214 | 215 | using (RandomNumberGenerator randomObject = CryptographyProviders.RngCreator()) 216 | { 217 | randomObject.GetBytes(_uniqueCardIdentifier, CardIdOffset, CardIdLength); 218 | } 219 | 220 | IsEmpty = false; 221 | } 222 | 223 | /// 224 | /// Set the CardIdentifier with the given value. If the array is 225 | /// not exactly 14 bytes, this method will throw an exception. 226 | /// 227 | /// 228 | /// If there is a CardId value already in this object, this method will 229 | /// overwrite it. 230 | /// 231 | /// 232 | /// The CardId to use. 233 | /// 234 | /// 235 | /// The data is not exactly 14 bytes. 236 | /// 237 | public void SetCardId(ReadOnlySpan cardIdValue) 238 | { 239 | _log.LogInformation("Set the CardId of CardCapabilityContainer with a caller-supplied value."); 240 | if (cardIdValue.Length != CardIdLength) 241 | { 242 | throw new ArgumentException( 243 | string.Format( 244 | CultureInfo.CurrentCulture, 245 | ExceptionMessages.InvalidPivDataObjectLength)); 246 | } 247 | 248 | Clear(); 249 | 250 | var dest = new Span(_uniqueCardIdentifier); 251 | cardIdValue.CopyTo(dest.Slice(CardIdOffset, CardIdLength)); 252 | IsEmpty = false; 253 | return; 254 | } 255 | 256 | /// 257 | public override byte[] Encode() 258 | { 259 | _log.LogInformation("Encode CardCapabilityContainer."); 260 | if (IsEmpty) 261 | { 262 | return new byte[] { 0x53, 0x00 }; 263 | } 264 | 265 | // We're encoding 266 | // 53 33 267 | // F0 15 268 | // A0 00 00 01 16 FF 02 269 | // --14 random bytes-- 270 | // F1 01 271 | // 21 272 | // F2 01 273 | // 21 274 | // F3 00 275 | // F4 01 276 | // 00 277 | // F5 01 278 | // 10 279 | // F6 00 280 | // F7 00 281 | // FA 00 282 | // FB 00 283 | // FC 00 284 | // FD 00 285 | // FE 00 286 | var tlvWriter = new TlvWriter(); 287 | using (tlvWriter.WriteNestedTlv(EncodingTag)) 288 | { 289 | tlvWriter.WriteValue(UniqueCardIdTag, UniqueCardIdentifier.Span); 290 | WriteFixedValues(tlvWriter); 291 | } 292 | 293 | byte[] returnValue = tlvWriter.Encode(); 294 | tlvWriter.Clear(); 295 | return returnValue; 296 | } 297 | 298 | /// 299 | public override bool TryDecode(ReadOnlyMemory encodedData) 300 | { 301 | _log.LogInformation("Decode data into CardCapabilityContainer."); 302 | if (encodedData.Length == 0) 303 | { 304 | Clear(); 305 | return true; 306 | } 307 | 308 | 309 | // We're looking for a CCC that is encoded as 310 | // 53 33 311 | // F0 15 312 | // A0 00 00 01 16 FF 02 313 | // --14 random bytes-- 314 | // F1 01 315 | // 21 316 | // F2 01 317 | // 21 318 | // F3 00 319 | // F4 01 320 | // 00 321 | // F5 01 322 | // 10 323 | // F6 00 324 | // F7 00 325 | // FA 00 326 | // FB 00 327 | // FC 00 328 | // FD 00 329 | // FE 00 330 | var tlvReader = new TlvReader(encodedData); 331 | bool isValid = tlvReader.TryReadNestedTlv(out tlvReader, EncodingTag); 332 | isValid = TryReadUniqueId(isValid, tlvReader); 333 | isValid = TryReadFixedValues(isValid, tlvReader); 334 | 335 | // If isValid is true, then we successfully decoded, so the object is 336 | // not empty (IsEmpty should be set to false). If isValid is false, 337 | // then the object is empty (IsEmpty should be set to true). 338 | IsEmpty = !isValid; 339 | 340 | return isValid; 341 | } 342 | 343 | // We're expecting F0 15 uniqueID with the first 7 bytes fixed. 344 | // Try to decode and verify the data is as expected. 345 | // If everything is correct, return true, otherwise, return false. 346 | // if the input isValid is false, don't bother doing anything, just 347 | // return false. 348 | private bool TryReadUniqueId(bool isValid, TlvReader tlvReader) 349 | { 350 | if (isValid) 351 | { 352 | _log.LogInformation("Decode data into CardCapabilityContainer: UniqueId."); 353 | if (tlvReader.TryReadValue(out ReadOnlyMemory encodedUniqueId, UniqueCardIdTag)) 354 | { 355 | if ((encodedUniqueId.Length == UniqueCardIdLength) && 356 | MemoryExtensions.SequenceEqual(encodedUniqueId.Slice(AidOffset, AidLength).Span, ApplicationIdentifier.Span)) 357 | { 358 | var dest = new Memory(_uniqueCardIdentifier); 359 | encodedUniqueId.CopyTo(dest); 360 | return true; 361 | } 362 | } 363 | } 364 | 365 | return false; 366 | } 367 | 368 | // We're expecting F1 throug FE (skipping F8 and F9). 369 | // Each of these is either Fx 01 byte or Fx 00. 370 | // Try to decode and verify the data is as expected. 371 | // If everything is correct, return true, otherwise, return false. 372 | // if the input isValid is false, don't bother doing anything, just 373 | // return false. 374 | private bool TryReadFixedValues(bool isValid, TlvReader tlvReader) 375 | { 376 | if (!isValid) 377 | { 378 | return false; 379 | } 380 | 381 | _log.LogInformation("Decode data into CardCapabilityContainer: FixedValues."); 382 | bool returnValue = isValid; 383 | 384 | Tuple[] elementList = GetFixedTupleArray(); 385 | 386 | int index = 0; 387 | while (returnValue && (index < elementList.Length)) 388 | { 389 | if (elementList[index].Item2 == 0) 390 | { 391 | returnValue = tlvReader.TryReadValue(out ReadOnlyMemory currentValue, elementList[index].Item1) && 392 | (currentValue.Length == elementList[index].Item2); 393 | } 394 | else 395 | { 396 | returnValue = tlvReader.TryReadByte(out byte currentValue, elementList[index].Item1) && 397 | (currentValue == elementList[index].Item3); 398 | } 399 | 400 | index++; 401 | } 402 | 403 | return returnValue; 404 | } 405 | 406 | private void WriteFixedValues(TlvWriter tlvWriter) 407 | { 408 | Tuple[] elementList = GetFixedTupleArray(); 409 | ReadOnlySpan emptySpan = ReadOnlySpan.Empty; 410 | 411 | int index = 0; 412 | do 413 | { 414 | if (elementList[index].Item2 == 0) 415 | { 416 | tlvWriter.WriteValue(elementList[index].Item1, emptySpan); 417 | } 418 | else 419 | { 420 | tlvWriter.WriteByte(elementList[index].Item1, elementList[index].Item3); 421 | } 422 | 423 | index++; 424 | } while (index < elementList.Length); 425 | } 426 | 427 | // This array of tuples represents what we'll be encoding or decoding. 428 | // Item 1 is the tag. 429 | // Item 2 is the length, it must be either 0 or 1. 430 | // Item 3 is the value, if the length is 0, this is ignored. 431 | private Tuple[] GetFixedTupleArray() => 432 | new Tuple[] { 433 | new Tuple(ContainerVersionTag, 1, ContainerVersionNumber), 434 | new Tuple(GrammarVersionTag, 1, GrammarVersionNumber), 435 | new Tuple(UnusedTag1, 0, 0), 436 | new Tuple(Pkcs15Tag, 1, Pkcs15Version), 437 | new Tuple(DataModelTag, 1, DataModelNumber), 438 | new Tuple(UnusedTag2, 0, 0), 439 | new Tuple(UnusedTag3, 0, 0), 440 | new Tuple(UnusedTag4, 0, 0), 441 | new Tuple(UnusedTag5, 0, 0), 442 | new Tuple(UnusedTag6, 0, 0), 443 | new Tuple(UnusedTag7, 0, 0), 444 | new Tuple(UnusedTag8, 0, 0) 445 | }; 446 | 447 | private void Clear() 448 | { 449 | var dataAsSpan = new Span(_uniqueCardIdentifier); 450 | dataAsSpan.Slice(CardIdOffset, CardIdLength).Clear(); 451 | IsEmpty = true; 452 | } 453 | 454 | /// 455 | protected override void Dispose(bool disposing) 456 | { 457 | if (_disposed) 458 | { 459 | return; 460 | } 461 | 462 | if (disposing) 463 | { 464 | Clear(); 465 | } 466 | 467 | base.Dispose(disposing); 468 | _disposed = true; 469 | } 470 | } 471 | } 472 | -------------------------------------------------------------------------------- /Yubikey/Objects/CardholderUniqueId.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Yubico AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Security.Cryptography; 17 | using System.Globalization; 18 | using Yubico.YubiKey.Cryptography; 19 | using Yubico.Core.Tlv; 20 | using Yubico.Core.Logging; 21 | 22 | namespace Yubico.YubiKey.Piv.Objects 23 | { 24 | /// 25 | /// Use this class to process the CHUID (CardHolder Unique IDentifier) data. 26 | /// 27 | /// 28 | /// A CHUID consists of five values: 29 | /// 30 | /// FASC-N (Federal Agency SmartCredential Number) 31 | /// GUID (Global Unique Identifier) 32 | /// Expiration Date 33 | /// Issuer Asymmetric Signature 34 | /// LRC (error code) 35 | /// 36 | /// 37 | /// For the YubiKey, the FASC-N and Expiration Date are fixed. That is, the 38 | /// FASC-N and Expiration Date are the same for all YubiKeys. 39 | /// 40 | /// 41 | /// The YubiKey does not use the signature value, and the PIV standard does 42 | /// not use the LRC. Hence, those two values are "empty". 43 | /// 44 | /// 45 | /// You can set the GUID to any 16-byte value you want, but it is generally a 46 | /// random value. That is so each YubiKey has a different GUID. 47 | /// 48 | /// 49 | /// You will generally get the current CHUID for a YubiKey using one of the 50 | /// PivSession.ReadObject methods. Upon manufacture, the CHUID is 51 | /// "empty", so the CardHolderUniqueId object will be empty as well 52 | /// (the property will be true). 53 | /// You can then set the GUID (or have a random GUID generated for you) and 54 | /// then store the CHUID using the PivSession.WriteObject method. 55 | /// 56 | /// 57 | /// It is also possible the CHUID is already set on the YubiKey. In that 58 | /// case, call one of the PivSession.ReadObject methods and the 59 | /// resulting object will have IsEmpty set to false and you can 60 | /// see the GUID that is on the YubiKey. 61 | /// 62 | /// 63 | /// Finally, you can create a new CardholderUniqueId object by calling 64 | /// the constructor directly, then set the GUID and call 65 | /// PivSession.WriteObject. That will, of course, overwrite the CHUID 66 | /// on the YubiKey, if there is one. Because that might not be something you 67 | /// want to do, this is the most dangerous option. 68 | /// 69 | /// 70 | /// See also the user's manual entry on 71 | /// PIV data objects. 72 | /// 73 | /// 74 | public sealed class CardholderUniqueId : PivDataObject 75 | { 76 | private const int ChuidDefinedDataTag = 0x005FC102; 77 | private const int GuidLength = 16; 78 | private const int EncodingTag = 0x53; 79 | private const int FascNumberTag = 0x30; 80 | private const int GuidTag = 0x34; 81 | private const int ExpirationDateTag = 0x35; 82 | private const string FixedDate = "20300101"; 83 | private const int FixedDateYear = 2030; 84 | private const int FixedDateMonth = 1; 85 | private const int FixedDateDay = 1; 86 | private const int SignatureTag = 0x3E; 87 | private const int LrcTag = 0xFE; 88 | 89 | private bool _disposed; 90 | private readonly Logger _log = Log.GetLogger(); 91 | 92 | /// 93 | /// The "Federal Agency Smart Credential Number" (FASC-N). This is a fixed 94 | /// 25-byte value for every YubiKey, and is a Non-Federal Issuer number. 95 | /// 96 | public ReadOnlyMemory FascNumber { get; private set; } 97 | 98 | private readonly byte[] _fascNumber = new byte[] { 99 | 0xd4, 0xe7, 0x39, 0xda, 0x73, 0x9c, 0xed, 0x39, 0xce, 0x73, 0x9d, 0x83, 0x68, 0x58, 0x21, 0x08, 100 | 0x42, 0x10, 0x84, 0x21, 0xc8, 0x42, 0x10, 0xc3, 0xeb 101 | }; 102 | 103 | /// 104 | /// The "Global Unique Identifier" (GUID). If there is no CHUID, this is 105 | /// "empty" (Guid.Length will be 0). This is a 16-byte value. 106 | /// 107 | public ReadOnlyMemory GuidValue { get; private set; } 108 | 109 | private byte[] _guidValue = new byte[GuidLength]; 110 | 111 | /// 112 | /// The PIV card's expiration date. This is a fixed value for every 113 | /// YubiKey: Jan 1, 2030. 114 | /// 115 | public DateTime ExpirationDate { get; private set; } 116 | 117 | /// 118 | /// Build a new object. This will not get a CHUID from any YubiKey, it 119 | /// will only build an "empty" object. 120 | /// 121 | /// 122 | /// To read the CHUID data out of a YubiKey, call the 123 | /// method. 124 | /// 125 | public CardholderUniqueId() 126 | { 127 | _log.LogInformation("Create a new instance of CardholderUniqueId."); 128 | _disposed = false; 129 | DataTag = ChuidDefinedDataTag; 130 | 131 | IsEmpty = true; 132 | FascNumber = new ReadOnlyMemory(_fascNumber); 133 | GuidValue = new ReadOnlyMemory(_guidValue); 134 | ExpirationDate = new DateTime(FixedDateYear, FixedDateMonth, FixedDateDay); 135 | } 136 | 137 | /// 138 | public override int GetDefinedDataTag() => ChuidDefinedDataTag; 139 | 140 | /// 141 | /// Set the Guid with a random, 16-byte value. 142 | /// 143 | /// 144 | /// This method will use the random number generator built by 145 | /// to generate 16 random bytes as 146 | /// the new GUID. 147 | /// 148 | /// If there is a GUID value already in this object, this method will 149 | /// overwrite it. 150 | /// 151 | /// 152 | public void SetRandomGuid() 153 | { 154 | _log.LogInformation("Set the GUID of CardholderUniqueId with a random value."); 155 | Clear(); 156 | 157 | using (RandomNumberGenerator randomObject = CryptographyProviders.RngCreator()) 158 | { 159 | randomObject.GetBytes(_guidValue, 0, GuidLength); 160 | } 161 | 162 | IsEmpty = false; 163 | } 164 | 165 | /// 166 | /// Set the Guid with the given value. If the array is not exactly 16 167 | /// bytes, this method will throw an exception. 168 | /// 169 | /// 170 | /// If there is a GUID value already in this object, this method will 171 | /// overwrite it. 172 | /// 173 | /// 174 | /// The GUID to use. 175 | /// 176 | /// 177 | /// The data is not exactly 16 bytes. 178 | /// 179 | public void SetGuid(ReadOnlySpan guidValue) 180 | { 181 | _log.LogInformation("Set the GUID of CardholderUniqueId with a caller-supplied value."); 182 | if (guidValue.Length != GuidLength) 183 | { 184 | throw new ArgumentException( 185 | string.Format( 186 | CultureInfo.CurrentCulture, 187 | ExceptionMessages.InvalidPivDataObjectLength)); 188 | } 189 | 190 | Clear(); 191 | 192 | var dest = new Span(_guidValue); 193 | guidValue.CopyTo(dest); 194 | IsEmpty = false; 195 | return; 196 | } 197 | 198 | /// 199 | public override byte[] Encode() 200 | { 201 | _log.LogInformation("Encode CardholderUniqueId."); 202 | if (IsEmpty) 203 | { 204 | return new byte[] { 0x53, 0x00 }; 205 | } 206 | 207 | // We're encoding 208 | // 53 3B 209 | // 30 19 210 | // d4 e7 39 da 73 9c ed 39 ce 73 9d 83 68 58 21 08 211 | // 42 10 84 21 c8 42 10 c3 eb 212 | // 34 10 213 | // GUID 214 | // 35 08 215 | // 32 30 33 30 30 31 30 31 216 | // 3e 00 217 | // fe 00 218 | var tlvWriter = new TlvWriter(); 219 | ReadOnlySpan emptySpan = ReadOnlySpan.Empty; 220 | using (tlvWriter.WriteNestedTlv(EncodingTag)) 221 | { 222 | tlvWriter.WriteValue(FascNumberTag, FascNumber.Span); 223 | tlvWriter.WriteValue(GuidTag, GuidValue.Span); 224 | tlvWriter.WriteString(ExpirationDateTag, FixedDate, System.Text.Encoding.ASCII); 225 | tlvWriter.WriteValue(SignatureTag, emptySpan); 226 | tlvWriter.WriteValue(LrcTag, emptySpan); 227 | } 228 | 229 | byte[] returnValue = tlvWriter.Encode(); 230 | tlvWriter.Clear(); 231 | return returnValue; 232 | } 233 | 234 | /// 235 | public override bool TryDecode(ReadOnlyMemory encodedData) 236 | { 237 | _log.LogInformation("Decode data into CardholderUniqueId."); 238 | Clear(); 239 | if (encodedData.Length == 0) 240 | { 241 | return true; 242 | } 243 | 244 | // We're looking for a CHUID that is encoded as 245 | // 53 3B 246 | // 30 19 247 | // d4 e7 39 da 73 9c ed 39 ce 73 9d 83 68 58 21 08 248 | // 42 10 84 21 c8 42 10 c3 eb 249 | // 34 10 250 | // <16 random bytes> 251 | // 35 08 252 | // 32 30 33 30 30 31 30 31 253 | // 3e 00 254 | // fe 00 255 | var tlvReader = new TlvReader(encodedData); 256 | bool isValid = tlvReader.TryReadNestedTlv(out tlvReader, EncodingTag); 257 | isValid = TryReadFascNumber(isValid, tlvReader); 258 | isValid = TryReadGuid(isValid, tlvReader); 259 | isValid = TryReadExpirationDate(isValid, tlvReader); 260 | isValid = TryReadTrailingElements(isValid, tlvReader); 261 | 262 | // If isValid is true, then we successfully decoded, so the object is 263 | // not empty (IsEmpty should be set to false). If isValid is false, 264 | // then the object is empty (IsEmpty should be set to true). 265 | IsEmpty = !isValid; 266 | 267 | return isValid; 268 | } 269 | 270 | // We're expecting 30 19 fasc-n with a fixed value. 271 | // Try to decode and verify the data is as expected. 272 | // If everything is correct, return true, otherwise, return false. 273 | // if the input isValid is false, don't bother doing anything, just 274 | // return false. 275 | private bool TryReadFascNumber(bool isValid, TlvReader tlvReader) 276 | { 277 | if (isValid) 278 | { 279 | _log.LogInformation("Decode data into CardholderUniqueId: FascNumber."); 280 | if (tlvReader.TryReadValue(out ReadOnlyMemory encodedFascn, FascNumberTag)) 281 | { 282 | if (MemoryExtensions.SequenceEqual(encodedFascn.Span, FascNumber.Span)) 283 | { 284 | var dest = new Memory(_fascNumber); 285 | encodedFascn.CopyTo(dest); 286 | return true; 287 | } 288 | } 289 | } 290 | 291 | return false; 292 | } 293 | 294 | // We're expecting 34 10 guid. 295 | // Try to decode and verify the data is as expected. 296 | // If everything is correct, return true, otherwise, return false. 297 | // if the input isValid is false, don't bother doing anything, just 298 | // return false. 299 | private bool TryReadGuid(bool isValid, TlvReader tlvReader) 300 | { 301 | if (isValid) 302 | { 303 | _log.LogInformation("Decode data into CardholderUniqueId: Guid."); 304 | if (tlvReader.TryReadValue(out ReadOnlyMemory encodedGuid, GuidTag)) 305 | { 306 | if (encodedGuid.Length == GuidLength) 307 | { 308 | var dest = new Memory(_guidValue); 309 | encodedGuid.CopyTo(dest); 310 | return true; 311 | } 312 | } 313 | } 314 | 315 | return false; 316 | } 317 | 318 | private bool TryReadExpirationDate(bool isValid, TlvReader tlvReader) 319 | { 320 | if (isValid) 321 | { 322 | _log.LogInformation("Decode data into CardholderUniqueId: ExpirationDate."); 323 | if (tlvReader.TryReadString(out string theDate, ExpirationDateTag, System.Text.Encoding.ASCII)) 324 | { 325 | if (theDate.Equals(FixedDate, StringComparison.Ordinal)) 326 | { 327 | ExpirationDate = new DateTime(FixedDateYear, FixedDateMonth, FixedDateDay); 328 | return true; 329 | } 330 | } 331 | } 332 | 333 | return false; 334 | } 335 | 336 | private bool TryReadTrailingElements(bool isValid, TlvReader tlvReader) 337 | { 338 | if (isValid) 339 | { 340 | _log.LogInformation("Decode data into CardholderUniqueId: TrailingElements."); 341 | if (tlvReader.TryReadValue(out ReadOnlyMemory signature, SignatureTag)) 342 | { 343 | if ((signature.Length == 0) && tlvReader.TryReadValue(out ReadOnlyMemory lrc, LrcTag)) 344 | { 345 | if ((lrc.Length == 0) && !tlvReader.HasData) 346 | { 347 | return true; 348 | } 349 | } 350 | } 351 | } 352 | 353 | return false; 354 | } 355 | 356 | private void Clear() 357 | { 358 | _guidValue = Guid.Empty.ToByteArray(); 359 | IsEmpty = true; 360 | } 361 | 362 | /// 363 | protected override void Dispose(bool disposing) 364 | { 365 | if (_disposed) 366 | { 367 | return; 368 | } 369 | 370 | if (disposing) 371 | { 372 | Clear(); 373 | } 374 | 375 | base.Dispose(disposing); 376 | _disposed = true; 377 | } 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /Yubikey/Objects/PivDataObject.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Yubico AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Globalization; 17 | 18 | namespace Yubico.YubiKey.Piv.Objects 19 | { 20 | /// 21 | /// This abstract class defines the basic properties of a PIV Application 22 | /// Data Object. 23 | /// 24 | /// 25 | /// Generally you will use one of the methods to 26 | /// get the specified data out of a YubiKey. The formatted data will be 27 | /// parsed and the resulting object will present the data in a more readable 28 | /// form. You can then update the data and call 29 | /// . 30 | /// 31 | /// Note that if there is no data on the YubiKey stored under the given 32 | /// object, then after calling ReadObject, the resulting 33 | /// PivDataObject will be "empty" () 34 | /// 35 | /// 36 | /// You can also create a new instance of a PivDataObject (call the 37 | /// constructor directly, rather than getting the YubiKey's contents), set 38 | /// it, and store it. However, when you store data (by calling 39 | /// WriteObject), you overwrite any data already there. Hence, you 40 | /// will likely want to get any data out first, to decide whether you want to 41 | /// change anything, rather than overwriting any possible contents sight 42 | /// unseen. 43 | /// 44 | /// 45 | /// This class (and each subclass) implements IDisposable because the 46 | /// data might be sensitive. Upon disposal, any stored data is overwritten. 47 | /// 48 | /// 49 | /// See also the user's manual entry on 50 | /// PIV data objects. 51 | /// 52 | /// 53 | public abstract class PivDataObject : IDisposable 54 | { 55 | private const int MinVendorDataTag = 0x005F0000; 56 | private const int MaxVendorDataTag = 0x005FFFFF; 57 | private const int MinPivDataTag = 0x005FC101; 58 | private const int MaxPivDataTag = 0x005FC123; 59 | private const int MinYubicoDataTag = 0x005FFF00; 60 | private const int MaxYubicoDataTag = 0x005FFF15; 61 | 62 | /// 63 | /// Indicates whether there is any data or not. If this is true, then 64 | /// the contents of any property are meaningless. 65 | /// 66 | /// 67 | /// Note that is it possible for some Data Objects to contain data, but 68 | /// all that data is "default" or "nothing". For example, the 69 | /// class contains numbers of certs and a URL. 70 | /// It is possible a YubiKey contains and encoded Key History in the Key 71 | /// History data location, but that data includes no certs and no URL. 72 | /// 73 | /// Suppose you build a KeyHistory object using the 74 | /// method, and the 75 | /// YubiKey contains data in the Key History storage area, but that data 76 | /// indicates there are no certs and no URL. The resulting object will 77 | /// not be empty (the IsEmpty field will be false). 78 | /// However, the properties describing the contents will be zero and 79 | /// NULL. 80 | /// 81 | /// 82 | /// If you build the KeyHistory object using the constructor, it 83 | /// will begin as empty, but if you set any properties, even to zero or 84 | /// null, the object will become not empty. 85 | /// 86 | /// 87 | public bool IsEmpty { get; protected set; } 88 | 89 | /// 90 | /// The value used to specify the storage location. 91 | /// 92 | /// 93 | /// Where, on the YubiKey, data is stored is determined by the 94 | /// DataTag. It is a number such as 0x005fC102 or 95 | /// 0x005FFF00. 96 | /// 97 | /// There are some tag values defined by the PIV standard, and there are 98 | /// others defined by Yubico (see the User's Manual entry on 99 | /// GET DATA and 100 | /// GET vendor data). 101 | /// In addition, some numbers are accepted by a YubiKey even though no 102 | /// one has defined their use or contents. These are the numbers 103 | /// 0x005F0000 through 0x005FFFFF (inclusive) not already 104 | /// specified. 105 | /// 106 | /// 107 | /// When you instantiate an object that is a subclass of this abstract 108 | /// class, this property will be set with the defined (or sometimes it's 109 | /// called the default) DataTag. However, it is possible to change 110 | /// that tag. See the User's manual entry on 111 | /// PIV data objects 112 | /// for more information on what valid data tags are possible. If you try 113 | /// to change to an unsupported tag, the SDK will throw an exception. 114 | /// 115 | /// 116 | /// Note that changing the DataTag is not recommended, but it is 117 | /// possible because there are some applications that have a use case for 118 | /// such a feature. See the User's Manual entry on 119 | /// PIV data objects. 120 | /// for a more detailed description of this topic. 121 | /// 122 | /// 123 | public int DataTag 124 | { 125 | get => _dataTag; 126 | set 127 | { 128 | if (!IsValidAlternateTag(value)) 129 | { 130 | throw new ArgumentException( 131 | string.Format( 132 | CultureInfo.CurrentCulture, 133 | ExceptionMessages.CannotUseDataTagAsAlternate, 134 | value)); 135 | } 136 | 137 | _dataTag = value; 138 | } 139 | } 140 | 141 | private int _dataTag; 142 | 143 | /// 144 | /// Is the given tag valid as an alternate? 145 | /// 146 | /// 147 | /// The data tag the caller wants to use as an alternate. 148 | /// 149 | /// 150 | /// A boolean, true is the given tag can be used as an alternate, 151 | /// false otherwise. 152 | /// 153 | protected virtual bool IsValidAlternateTag(int dataTag) 154 | { 155 | if (dataTag != GetDefinedDataTag()) 156 | { 157 | if ((dataTag < MinVendorDataTag) || (dataTag > MaxVendorDataTag) 158 | || ((dataTag >= MinPivDataTag) && (dataTag <= MaxPivDataTag)) 159 | || ((dataTag >= MinYubicoDataTag) && (dataTag <= MaxYubicoDataTag))) 160 | { 161 | return false; 162 | } 163 | } 164 | 165 | return true; 166 | } 167 | 168 | /// 169 | /// Get the defined data tag. This is the data tag that the PIV 170 | /// standard or Yubico defines to specify the given data object. 171 | /// 172 | /// 173 | /// This is also called the default data tag. This method will always 174 | /// return the defined tag, regardless of what the DataTag 175 | /// property returns. That is, even if you change the DataTag this 176 | /// method will still return the original, defined tag. 177 | /// 178 | /// 179 | /// The data tag defined for the data object. 180 | /// 181 | public abstract int GetDefinedDataTag(); 182 | 183 | /// 184 | /// Build the encoding of the data. 185 | /// 186 | /// 187 | /// Each data object has a defined format. See the User's Manual entry on 188 | /// GET DATA and 189 | /// GET vendor data 190 | /// for descriptions of the formats. This method will build a new byte 191 | /// array containing the data set in the object. This data will generally 192 | /// then be stored on the YubiKey. 193 | /// 194 | /// Note that this method returns a new byte array, not a reference to an 195 | /// array inside the object. If this array contains any sensitive data, 196 | /// make sure you overwrite it when done with it. 197 | /// 198 | /// 199 | /// If the object is empty (IsEmpty is true), then this 200 | /// method will return the encoding of no data, which is 0x53 00. 201 | /// 202 | /// 203 | /// 204 | /// A new byte array containing the encoded data object. 205 | /// 206 | public abstract byte[] Encode(); 207 | 208 | /// 209 | /// Decode the data given according to the format specified for the data 210 | /// object. 211 | /// 212 | /// 213 | /// This will parse the encoding and set local properties with the data. 214 | /// The encodedData generally was retrieved from the YubiKey. 215 | /// 216 | /// This will replace any data in the object. 217 | /// 218 | /// 219 | /// If there is no data (encodedData.Length is 0) this method will 220 | /// set the object to the empty state (IsEmpty will be true 221 | /// and the contents of any data properties will be meaningless). 222 | /// 223 | /// 224 | /// If the input is not encoded as expected, this method will throw an 225 | /// exception. This includes the fixed values. That is, there are some 226 | /// values in some data objects that are fixed for every YubiKey, and 227 | /// this method will expect the contents of the encodedData to 228 | /// contain those fixed values. 229 | /// 230 | /// 231 | /// 232 | /// The data to parse. 233 | /// 234 | /// 235 | /// The data is not properly encoded for the data object. 236 | /// 237 | public void Decode(ReadOnlyMemory encodedData) 238 | { 239 | if (!TryDecode(encodedData)) 240 | { 241 | throw new ArgumentException( 242 | string.Format( 243 | CultureInfo.CurrentCulture, 244 | ExceptionMessages.InvalidDataEncoding)); 245 | } 246 | } 247 | 248 | /// 249 | /// Try to decode the data given according to the format specified for 250 | /// the data object. If successful, return true, otherwise, return 251 | /// false. 252 | /// 253 | /// 254 | /// This will parse the encoding and set local properties with the data. 255 | /// The encodedData generally was retrieved from the YubiKey. 256 | /// 257 | /// This will replace any data in the object. 258 | /// 259 | /// 260 | /// If there is no data (encodedData.Length is 0) this method will 261 | /// set the object to the empty state (IsEmpty will be true 262 | /// and the contents of any data properties will be meaningless) and 263 | /// return true. 264 | /// 265 | /// 266 | /// If the input is not encoded as expected, this method will set the 267 | /// object to the empty state and return false. This includes the 268 | /// fixed values. That is, there are some values in some data objects 269 | /// that are fixed for every YubiKey, and this method will expect the 270 | /// contents of the encodedData to contain those fixed values. 271 | /// 272 | /// 273 | /// If the input is encoded as expected, yet the data in that encoding is 274 | /// invalid (e.g. some element is not the correct length), this method 275 | /// will return false. 276 | /// 277 | /// 278 | /// 279 | /// The data to parse. 280 | /// 281 | /// 282 | /// A boolean, true if the method successfully decodes, 283 | /// false otherwise. 284 | /// 285 | /// 286 | /// The data is not properly encoded for the data object. 287 | /// 288 | public abstract bool TryDecode(ReadOnlyMemory encodedData); 289 | 290 | /// 291 | /// Releases any unmanaged resources and overwrites any sensitive data. 292 | /// 293 | public void Dispose() 294 | { 295 | Dispose(disposing: true); 296 | GC.SuppressFinalize(this); 297 | } 298 | 299 | /// 300 | /// Releases any unmanaged resources and overwrites any sensitive data. 301 | /// 302 | protected virtual void Dispose(bool disposing) 303 | { 304 | } 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /Yubikey/Resources/Core/ExceptionMessages.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | A command APDU has returned an error in the status word. Check ApduStatus and other properties for more details. 122 | 123 | 124 | The specified device property is not in the set of supported properties for this API. 125 | 126 | 127 | The provided string was not of even length. 128 | 129 | 130 | Encountered a USB interface which appeared to be supported by the HID driver, but was unable to attach the driver. 131 | 132 | 133 | [{0}] is not a supported HID class. Only generic HidClass and Keyboard are supported at this time. 134 | 135 | 136 | Unable to open connection to NFC device. The tag may have been removed. 137 | 138 | 139 | Response APDUs must contain at least 2 bytes for status. The data passed in has a [{0}] byte length. 140 | 141 | 142 | Encountered an error communicating with the smart card subsystem. 143 | 144 | 145 | Encountered an error in the Linux udev library. 146 | 147 | 148 | It is not possible to restart listening for device updates after stopping. 149 | 150 | 151 | Unexpected end of buffer when trying to parse a TLV data structure. 152 | 153 | 154 | Cannot parse element with either an initial octet of 80 or a length field longer than 4 bytes. 155 | 156 | 157 | Cannot parse element with tag longer than 2 bytes. 158 | 159 | 160 | The TLV reading code encountered an unexpected tag or value. 161 | 162 | 163 | The input does not build a valid schema. 164 | 165 | 166 | An unspecified error has been raised during the issuance of a command APDU. 167 | 168 | 169 | Encountered a Platform API exception of unknown origin. 170 | 171 | 172 | Attempted to access interface index [{0}]. The interface list only contains [{1}] elements. 173 | 174 | 175 | CommandApdu field [{0}] out of range for [{1}] APDU encoding. 176 | 177 | 178 | No valid ApduEncoding found. 179 | 180 | 181 | Ne must be a non-negative integer. 182 | 183 | 184 | Unable to establish a connection to the platform's smartcard subsystem. 185 | 186 | 187 | Unable to establish a connection to the smart card in [{0}]. 188 | 189 | 190 | An error occurred while attempting to retrieve the list of smart card readers. 191 | 192 | 193 | Unable to begin a transaction with the given smart card. 194 | 195 | 196 | Encountered an error while attempting to transmit data to a smart card. 197 | 198 | 199 | An error occurred while attempting to get the current status of the smart cards available to the system. 200 | 201 | 202 | The smart card subsystem indicated success when enumerating smart cards, however no data was returned. 203 | 204 | 205 | Failed to resolve the native function [{0}]. 206 | 207 | 208 | Could not load library [{0}]. 209 | 210 | 211 | Unable to reconnect to the smart card. 212 | 213 | 214 | Encountered unexpected property type on an IOKit device. Expected [{0}] but encountered [{1}]. 215 | 216 | 217 | No IOKit registry entry was found for the device. 218 | 219 | 220 | Can't open IOKit device. 221 | 222 | 223 | The IOKit operation [{0}] failed. 224 | 225 | 226 | The runloop used for the IOKit operation did not complete successfully. Result was [{0}]. 227 | 228 | 229 | Could not open HID node on Linux. 230 | 231 | 232 | A HIDRAW operation failed. 233 | 234 | 235 | The file handle cannot be null. 236 | 237 | 238 | Keyboard layout [{0}] doesn't have HID code [0x{1}] 239 | 240 | 241 | Keyboard layout [{0}] doesn't have an HID code for [{1}]. 242 | 243 | 244 | Illegal character: {0}. 245 | 246 | 247 | The value, 0x{0x2}, is not a valid Base32 digit. 248 | 249 | 250 | The buffer length must be large enough to hold the report, plus one additional byte that specifies a nonzero report ID or zero. 251 | 252 | 253 | The output span is not sufficient to contain the encoded output. 254 | 255 | 256 | The output span is not sufficient to contain the decoded output. 257 | 258 | 259 | Invalid digit (0x{0}). Digit value must be 0x0 to 0x{1}. 260 | 261 | 262 | Encountered an error in the Config Manager library. 263 | 264 | -------------------------------------------------------------------------------- /Yubikey/Tlv/TlvEncoder.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Yubico AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Collections.Generic; 17 | 18 | namespace Yubico.Core.Tlv 19 | { 20 | /// 21 | /// An interface for representing a class that can encode itself into a TLV: 22 | /// tag || length || value 23 | /// 24 | /// 25 | /// Classes that implement this interface can be called upon to build a TLV 26 | /// based on the tag and value loaded. The length is encoded following the 27 | /// DER rules. E.g. decimal 32 is encoded as 0x20, 128 is 0x81 80, and so on. 28 | /// 29 | /// It is possible a class that implements this will be a single TLV, or a 30 | /// nested construction, namely TL { TLV, TLV, ..., TLV }. 31 | /// 32 | /// 33 | internal abstract class TlvEncoder 34 | { 35 | private const int MaximumTag = 0x0000FFFF; 36 | private const int MaximumLength = 0x00FFFFFF; 37 | // The longest TL we support would be a two-byte tag with a length that 38 | // requires 4 bytes (83 plus three bytes). Something like this: 39 | // 5F 50 83 01 00 01 40 | // So the maximum length of the length is 4 and the 41 | // maximum length of the tag + length is 6. 42 | private const int MaximumLengthByteCount = 4; 43 | private const int MaximumTagLengthLength = 6; 44 | 45 | /// 46 | /// How long will the encoding of this element or NestedTlv be? 47 | /// 48 | public abstract int EncodedLength { get; } 49 | 50 | /// 51 | /// Build a buffer that holds the tag and length. 52 | /// 53 | /// 54 | /// The tag might be one or two bytes, the length might be one byte, it 55 | /// might be 81 xx, 82 xx xx, and so on. 56 | /// 57 | /// This method will verify the tag is supported (any two-byte tag is 58 | /// supported, so any value >=0 and <= 0x0000FFFF). It will also verify 59 | /// the length is supported (any length >=0 and <= 0x00FFFFFF). 60 | /// 61 | /// 62 | /// 63 | /// The tag to write out. 64 | /// 65 | /// 66 | /// The length to write out. 67 | /// 68 | /// 69 | /// A new byte array containing the tag and length. 70 | /// 71 | /// 72 | /// The tag or length is unsupported. 73 | /// 74 | public static byte[] BuildTagAndLength(int tag, int length) 75 | { 76 | VerifyTag(tag); 77 | VerifyLength(length); 78 | byte[] encoding = new byte[MaximumTagLengthLength]; 79 | 80 | int index = 0; 81 | if (tag > 0xFF) 82 | { 83 | encoding[index] = unchecked((byte)(tag >> 8)); 84 | index++; 85 | } 86 | encoding[index] = (byte)tag; 87 | index++; 88 | 89 | byte[] fullLength = new byte[] { 0x83, 0x82, 0x81, (byte)length }; 90 | int count = 1; 91 | if (length > 0x7F) 92 | { 93 | count++; 94 | if ((length & 0x00FFFF00) != 0) 95 | { 96 | count++; 97 | fullLength[2] = (byte)((length & 0x0000FF00) >> 8); 98 | 99 | if ((length & 0x00FF0000) != 0) 100 | { 101 | count++; 102 | fullLength[1] = (byte)((length & 0x00FF0000) >> 16); 103 | } 104 | } 105 | } 106 | 107 | Array.Copy(fullLength, MaximumLengthByteCount - count, encoding, index, count); 108 | Array.Resize(ref encoding, count + index); 109 | 110 | return encoding; 111 | } 112 | 113 | /// 114 | /// Verify that the tag is valid, that it is a tag the TLV code supports. 115 | /// 116 | /// 117 | /// If the tag is valid, the method returns. If not, it throws an 118 | /// exception. 119 | /// 120 | /// Currently supported are one- and two-byte tags, so any input tag that 121 | /// is >=0 and <= 0x0000FFFF will be valid. 122 | /// 123 | /// 124 | /// 125 | /// The tag to verify. 126 | /// 127 | public static void VerifyTag(int tag) 128 | { 129 | if ((tag < 0) || (tag > MaximumTag)) 130 | { 131 | throw new TlvException("Unsupported TLV Tag"); 132 | } 133 | } 134 | 135 | /// 136 | /// Verify that the length is valid, that it is a length the TLV code 137 | /// supports. 138 | /// 139 | /// 140 | /// If the length is valid, the method returns. If not, it throws an 141 | /// exception. 142 | /// 143 | /// Currently supported are lengths from 0 to 0x00FFFFFF. 144 | /// 145 | /// 146 | /// 147 | /// The length to verify. 148 | /// 149 | public static void VerifyLength(int length) 150 | { 151 | if ((length < 0) || (length > MaximumLength)) 152 | { 153 | throw new TlvException("Unsupported TLV length"); 154 | } 155 | } 156 | 157 | /// 158 | /// Place the TLV of this element into the given Span, beginning 159 | /// at offset. 160 | /// 161 | /// 162 | /// If the size of the Span is not big enough to hold the TLV, it will 163 | /// return false and bytesWritten will be set to 0. 164 | /// 165 | /// 166 | /// The buffer into which the TLV will be placed. 167 | /// 168 | /// 169 | /// The offset into encoding where the method will begin placing the TLV. 170 | /// 171 | /// 172 | /// On success, receives the number of bytes written into the encoding. 173 | /// 174 | /// 175 | /// A bool, true if the method successfully encoded, false otherwise. 176 | /// 177 | public abstract bool TryEncode(Span encoding, int offset, out int bytesWritten); 178 | 179 | /// 180 | /// Clear any data that had been copied from input. 181 | /// 182 | /// 183 | /// If any of the data to encode had been sensitive (such as private key 184 | /// material), then call the Clear method after encoding to make sure it 185 | /// is overwritten. 186 | /// 187 | public abstract void Clear(); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /Yubikey/Tlv/TlvException.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Yubico AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Runtime.Serialization; 17 | 18 | namespace Yubico.Core.Tlv 19 | { 20 | [Serializable] 21 | public class TlvException : InvalidOperationException 22 | { 23 | public TlvException() 24 | { 25 | 26 | } 27 | 28 | public TlvException(string message) : 29 | base(message) 30 | { 31 | 32 | } 33 | 34 | public TlvException(string message, Exception innerException) : 35 | base(message, innerException) 36 | { 37 | 38 | } 39 | 40 | protected TlvException(SerializationInfo serializationInfo, StreamingContext streamingContext) : 41 | base(serializationInfo, streamingContext) 42 | { 43 | 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Yubikey/Tlv/TlvNestedTlv.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Yubico AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Collections.Generic; 17 | 18 | namespace Yubico.Core.Tlv 19 | { 20 | /// 21 | /// A Tag-Length-Value that has sub-elements. 22 | /// 23 | /// 24 | /// There are two types of Nested TLV. One, the sub-elements are presented as a 25 | /// concatenation: 26 | /// 27 | /// TLV || TLV || ... TLV 28 | /// for example: 81 02 05 05 82 01 14 83 04 00 72 9A 1E 29 | /// 81 02 05 05 82 01 14 83 04 00 72 9A 1E 30 | /// 31 | /// And two, a "tree" where the Nested TLV has a tag and the value is a 32 | /// collection of sub-elements: 33 | /// 34 | /// TL { TLV || TLV || ... TLV } 35 | /// for example: 7C 0D 81 02 05 05 82 01 14 83 04 00 72 9A 1E 36 | /// 7C 0D 37 | /// 81 02 38 | /// 05 05 39 | /// 82 01 40 | /// 14 41 | /// 83 04 42 | /// 00 72 9A 1E 43 | /// 44 | /// 45 | internal class TlvNestedTlv : TlvEncoder 46 | { 47 | private int _encodedLength; 48 | 49 | /// 50 | override public int EncodedLength => _encodedLength; 51 | 52 | private readonly List _subElements = new List(); 53 | private int _subElementLength; 54 | private readonly int _tag; 55 | private byte[] _tagAndLength; 56 | 57 | /// 58 | /// Build a new NestedTlv that will organize as a concatenation. 59 | /// 60 | /// 61 | /// The sub-elements added to this Nested TLV will be encoded as a 62 | /// concatenation: 63 | /// 64 | /// TLV || TLV || ... TLV 65 | /// for example: 81 02 05 05 82 01 14 83 04 00 72 9A 1E 66 | /// 81 02 05 05 82 01 14 83 04 00 72 9A 1E 67 | /// 68 | /// 69 | public TlvNestedTlv() 70 | { 71 | _tagAndLength = Array.Empty(); 72 | _encodedLength = 0; 73 | } 74 | 75 | /// 76 | /// Build a new NestedTlv that will organize as a tree with the given tag. 77 | /// 78 | /// 79 | /// The sub-elements added to this Nested TLV will be encoded as a tree: 80 | /// 81 | /// var ykInfo = new TlvNestedTlv(0x7C);
82 | /// TL { TLV || TLV || ... TLV } 83 | /// for example: 7C 0D 81 02 05 05 82 01 14 83 04 00 72 9A 1E 84 | /// 7C 0D 85 | /// 81 02 86 | /// 05 05 87 | /// 82 01 88 | /// 14 89 | /// 83 04 90 | /// 00 72 9A 1E 91 | ///
92 | ///
93 | /// 94 | /// The tag is unsupported. 95 | /// 96 | public TlvNestedTlv(int tag) 97 | { 98 | _tag = tag; 99 | _tagAndLength = BuildTagAndLength(tag, 0); 100 | _encodedLength = _tagAndLength.Length; 101 | } 102 | 103 | /// 104 | /// Add a new sub-element to this Nested TLV. 105 | /// 106 | /// 107 | /// The subElement might be a TlvSubElement, it might be a TlvNestedTlv 108 | /// as well. 109 | /// 110 | /// 111 | /// The sub-element to add. 112 | /// 113 | /// 114 | /// The tag or length is unsupported. 115 | /// 116 | public void AddSubElement(TlvEncoder subElement) 117 | { 118 | _subElements.Add(subElement); 119 | _subElementLength += subElement.EncodedLength; 120 | if (_tagAndLength.Length != 0) 121 | { 122 | _tagAndLength = BuildTagAndLength(_tag, _subElementLength); 123 | } 124 | _encodedLength = _tagAndLength.Length + _subElementLength; 125 | } 126 | 127 | /// 128 | override public bool TryEncode(Span encoding, int offset, out int bytesWritten) 129 | { 130 | bytesWritten = 0; 131 | if (encoding.Length < (offset + _encodedLength)) 132 | { 133 | return false; 134 | } 135 | 136 | if (_tagAndLength.Length != 0) 137 | { 138 | Span destination = encoding.Slice(offset); 139 | var source = new Span(_tagAndLength); 140 | source.CopyTo(destination); 141 | offset += _tagAndLength.Length; 142 | } 143 | 144 | foreach (TlvEncoder element in _subElements) 145 | { 146 | if (element.TryEncode(encoding, offset, out int encodingLength) == false) 147 | { 148 | return false; 149 | } 150 | offset += encodingLength; 151 | } 152 | 153 | bytesWritten = _encodedLength; 154 | return true; 155 | } 156 | 157 | /// 158 | override public void Clear() 159 | { 160 | foreach (TlvEncoder element in _subElements) 161 | { 162 | element.Clear(); 163 | } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /Yubikey/Tlv/TlvSubElement.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Yubico AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Security.Cryptography; 17 | 18 | namespace Yubico.Core.Tlv 19 | { 20 | /// 21 | /// A single Tag-Length-Value entry. 22 | /// 23 | internal class TlvSubElement : TlvEncoder 24 | { 25 | private readonly int _encodedLength; 26 | 27 | /// 28 | override public int EncodedLength => _encodedLength; 29 | 30 | private readonly byte[] _tagAndLength; 31 | private readonly byte[] _value; 32 | 33 | // The default constructor explicitly defined. We don't want it to be 34 | // used. 35 | private TlvSubElement() 36 | { 37 | throw new NotImplementedException(); 38 | } 39 | 40 | /// 41 | /// Create a new TLV object that will be able to encode itself with the 42 | /// given tag and value. 43 | /// 44 | /// 45 | /// This class supports one- or two-byte tags, an input tag of a negative 46 | /// number or a number greater than 0x0000FFFF will result in an 47 | /// exception. 48 | /// 49 | /// The value that will be written out is the byte array provided 50 | /// (essentially an Array.Copy). It is the responsibility of the caller 51 | /// to format the value into an appropriate byte array if necessary. 52 | /// 53 | /// 54 | /// If there is no data, pass an empty Span: 55 | /// ReadOnlySpan<byte>.Empty. In that case, what is written 56 | /// out is simply tag 00. 57 | /// 58 | /// 59 | /// 60 | /// The tag that will be written out. 61 | /// 62 | /// 63 | /// The value to write out. 64 | /// 65 | /// 66 | /// The tag is invalid, or the length is unsupported. 67 | /// 68 | public TlvSubElement(int tag, ReadOnlySpan value) 69 | { 70 | _tagAndLength = BuildTagAndLength(tag, value.Length); 71 | _value = value.ToArray(); 72 | 73 | _encodedLength = _tagAndLength.Length + _value.Length; 74 | } 75 | 76 | /// 77 | /// Create a new TLV object with the given encoded byte array. 78 | /// 79 | /// 80 | /// If an object is created using this constructor, 81 | /// the data given is assumed to be a full TLV encoded element. 82 | /// Whatever is passed in will be written out as the encoding. 83 | /// The class will not verify the tag or length. 84 | /// 85 | /// 86 | /// The encoded byte array that will be written out. 87 | /// 88 | public TlvSubElement(ReadOnlySpan encodedTlv) 89 | { 90 | _tagAndLength = Array.Empty(); 91 | _value = encodedTlv.ToArray(); 92 | 93 | _encodedLength = _value.Length; 94 | } 95 | 96 | /// 97 | override public bool TryEncode(Span encoding, int offset, out int bytesWritten) 98 | { 99 | bytesWritten = 0; 100 | if (encoding.Length < (offset + _encodedLength)) 101 | { 102 | return false; 103 | } 104 | 105 | Span destination = encoding.Slice(offset); 106 | _tagAndLength.AsSpan().CopyTo(destination); 107 | 108 | destination = encoding.Slice(offset + _tagAndLength.Length); 109 | _value.AsSpan().CopyTo(destination); 110 | 111 | bytesWritten = _encodedLength; 112 | return true; 113 | } 114 | 115 | /// 116 | override public void Clear() => _value.AsSpan().Clear(); 117 | } 118 | } 119 | --------------------------------------------------------------------------------