/* * Copyright (c) 2023 Vaughn Nugent * * Package: PkiAuthenticator * File: SoftwareAuthenticator.cs * * PkiAuthenticator is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 2 of the License, * or (at your option) any later version. * * PkiAuthenticator is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with PkiAuthenticator. If not, see http://www.gnu.org/licenses/. */ using System; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using VNLib.Utils; using VNLib.Utils.IO; using VNLib.Utils.Logging; using VNLib.Utils.Memory; using Yubico.YubiKey.Piv; using static PkiAuthenticator.Statics; namespace PkiAuthenticator { /// /// Provies a certificate/private key software based authenticator /// public sealed class SoftwareAuthenticator : VnDisposeable, IAuthenticator { private X509Certificate2? _certFile; private byte[]? _certFileData; /// public PivAlgorithm KeyAlgorithm { get; private set; } public int RequiredBufferSize { get { return KeyAlgorithm switch { PivAlgorithm.Rsa1024 => 128, PivAlgorithm.Rsa2048 => 256, PivAlgorithm.EccP256 => 128, PivAlgorithm.EccP384 => 256, _ => 128, }; } } /// public bool Initialize() { Log.Debug("Using software authenticator"); //try to import the certificate file string? cerFilePath = CliArgs.GetArg("--software"); if(cerFilePath == null) { Log.Error("You must specify a file path following the --software flag"); return false; } //Check if the file exists if (!FileOperations.FileExists(cerFilePath)) { Log.Error("The certificate file does not exist"); return false; } string? privateKeyFile = CliArgs.GetArg("--private-key"); if(privateKeyFile == null) { Log.Error("You must specify a private key pem file using the --private-key 'priv.pem' flag"); return false; } //Confirm private key file exists if(!FileOperations.FileExists(privateKeyFile)) { Log.Error("The private key file does not exist"); return false; } ReadOnlySpan password = null; //See if password is required if (CliArgs.HasArg("--password")) { //encryption is required, get from arg, or from env var string? pass = CliArgs.GetArg("--password") ?? Environment.GetEnvironmentVariable(Program.SOFTWARE_PASSWORD_VAR_NAME); if (pass == null) { //if silent, we cant read the key, so we need to bail; if (CliArgs.Silent) { return false; } //Read key from stdin Log.Information("Please enter your private key password"); pass = Console.ReadLine(); } password = pass; } //file is a pem certificate try { //file is encrypted if (password.IsEmpty) { Log.Debug("Importing raw pem/private key x509 certificate file"); //Non encrypted _certFile = X509Certificate2.CreateFromPemFile(cerFilePath, privateKeyFile); } else { Log.Debug("Importing encyrpted pem/private key x509 certificate file"); //load and decrypt _certFile = X509Certificate2.CreateFromEncryptedPemFile(cerFilePath, password, privateKeyFile); } //Get the raw file data _certFileData = _certFile.GetRawCertData(); //Try get rsa key, just get pubkey to discover alg info using(RSA? alg = _certFile.GetRSAPublicKey()) { if (alg != null) { switch (alg.KeySize) { case 1024: KeyAlgorithm = PivAlgorithm.Rsa1024; break; case 2048: KeyAlgorithm = PivAlgorithm.Rsa2048; break; default: Log.Error("The certificate uses an unspported keyalgorithm"); return false; } } } //Try get ecdsa alg using(ECDsa? alg = _certFile.GetECDsaPublicKey()) { if (alg != null) { switch (alg.KeySize) { case 256: KeyAlgorithm = PivAlgorithm.EccP256; break; case 384: KeyAlgorithm = PivAlgorithm.EccP384; break; default: Log.Error("The certificate uses an unspported keyalgorithm"); return false; } } } return true; } catch(Exception ex) { //Write the entire stack trace to the log if in debug mode if (Log.IsEnabled(LogLevel.Debug)) { Log.Error(ex); } else { Log.Error("Failed to import the certificate file, reason {r}", ex.Message); } } return false; } /// public X509Certificate2 GetCertificate() { Check(); return new(_certFileData); } /// public int ListDevices() { Log.Error("List devices is not supported in software mode"); return -1; } protected override void Free() { //Dispose cert file _certFile?.Dispose(); //Zero the cert file data buffer MemoryUtil.InitializeBlock(_certFileData.AsSpan()); } HashAlgorithmName GetAlgName() { return KeyAlgorithm switch { PivAlgorithm.Rsa1024 => HashAlgorithmName.SHA256,//PS256 PivAlgorithm.Rsa2048 => HashAlgorithmName.SHA256,//PS256 PivAlgorithm.EccP256 => HashAlgorithmName.SHA256,//ES256 PivAlgorithm.EccP384 => HashAlgorithmName.SHA384,//ES384 _ => throw new NotSupportedException("Hash algorithim is not supported by this key"), }; } /// public ERRNO ComputeSignatureFromHash(ReadOnlySpan hash, Span outputBuffer) { Check(); switch (KeyAlgorithm) { case PivAlgorithm.Rsa1024: case PivAlgorithm.Rsa2048: { //Try load private keys from cert using RSA rsa = _certFile.GetRSAPrivateKey()!; //Signs the data using sha256 if (!rsa.TrySignHash(hash, outputBuffer, GetAlgName(), RSASignaturePadding.Pkcs1, out int written)) { throw new InternalBufferTooSmallException(""); } return written; } case PivAlgorithm.EccP256: case PivAlgorithm.EccP384: { using ECDsa ecc = _certFile.GetECDsaPrivateKey()!; //Sign the digest if (!ecc!.TrySignHash(hash, outputBuffer, DSASignatureFormat.IeeeP1363FixedFieldConcatenation, out int written)) { throw new InternalBufferTooSmallException(""); } return written; } //This case should never be hit default: throw new CryptographicException("Cannot sign data, the algorithm is unsupported"); } } } }