diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/SignatureAlgorithmInstance.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/SignatureAlgorithmInstance.qll new file mode 100644 index 000000000000..1a7d9846c350 --- /dev/null +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/SignatureAlgorithmInstance.qll @@ -0,0 +1,103 @@ +import cpp +private import experimental.quantum.Language +private import KnownAlgorithmConstants +private import Crypto::KeyOpAlg as KeyOpAlg +private import OpenSSLAlgorithmInstanceBase +private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumerBase +private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.DirectAlgorithmValueConsumer +private import AlgToAVCFlow + + +/** + * Gets the signature algorithm type based on the normalized algorithm name. + */ +private predicate knownOpenSSLConstantToSignatureFamilyType( + KnownOpenSSLSignatureAlgorithmConstant e, Crypto::KeyOpAlg::TAlgorithm type +) { + exists(string name | + name = e.getNormalizedName() and + ( + name.matches("rsa%") and type = KeyOpAlg::TAsymmetricCipher(KeyOpAlg::RSA()) + or + name.matches("dsa%") and type = KeyOpAlg::TSignature(KeyOpAlg::DSA()) + or + name.matches("ecdsa%") and type = KeyOpAlg::TSignature(KeyOpAlg::ECDSA()) + or + name.matches("ed25519%") and type = KeyOpAlg::TSignature(KeyOpAlg::Ed25519()) + or + name.matches("ed448%") and type = KeyOpAlg::TSignature(KeyOpAlg::Ed448()) + // or + // name.matches("sm2%") and type = KeyOpAlg::TSignature(KeyOpAlg::SM2()) + // or + // name.matches("ml-dsa%") and type = KeyOpAlg::TSignature(KeyOpAlg::MLDSA()) + // or + // name.matches("slh-dsa%") and type = KeyOpAlg::TSignature(KeyOpAlg::SLHDSA()) + ) + ) +} + +/** + * A signature algorithm instance derived from an OpenSSL constant. + */ +class KnownOpenSSLSignatureConstantAlgorithmInstance extends OpenSSLAlgorithmInstance, + Crypto::KeyOperationAlgorithmInstance instanceof KnownOpenSSLSignatureAlgorithmConstant +{ + OpenSSLAlgorithmValueConsumer getterCall; + + KnownOpenSSLSignatureConstantAlgorithmInstance() { + // Two possibilities: + // 1) The source is a literal and flows to a getter, then we know we have an instance + // 2) The source is a KnownOpenSSLAlgorithm call, and we know we have an instance immediately from that + + // Possibility 1: + this instanceof Literal and + exists(DataFlow::Node src, DataFlow::Node sink | + // Sink is an argument to a signature getter call + sink = getterCall.getInputNode() and + // Source is `this` + src.asExpr() = this and + // This traces to a getter + KnownOpenSSLAlgorithmToAlgorithmValueConsumerFlow::flow(src, sink) + ) + or + // Possibility 2: + this instanceof DirectAlgorithmValueConsumer and getterCall = this + } + + override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { + none() + } + + override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { + none() + } + + override string getRawAlgorithmName() { result = this.(Literal).getValue().toString() } + + override int getKeySizeFixed() { + // this.(KnownOpenSSLSignatureAlgorithmConstant).getExplicitKeySize() = result + none() + } + + override KeyOpAlg::Algorithm getAlgorithmType() { + knownOpenSSLConstantToSignatureFamilyType(this, result) + or + not knownOpenSSLConstantToSignatureFamilyType(this, _) and + result = KeyOpAlg::TSignature(KeyOpAlg::OtherSignatureAlgorithmType()) + } + + override OpenSSLAlgorithmValueConsumer getAVC() { result = getterCall } + + override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { + // TODO: trace to any key size initializer, symmetric and asymmetric + none() + } + + override predicate shouldHaveModeOfOperation() { + none() + } + + override predicate shouldHavePaddingScheme() { + none() + } +} diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmValueConsumers/SignatureAlgorithmValueConsumer.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmValueConsumers/SignatureAlgorithmValueConsumer.qll new file mode 100644 index 000000000000..b25a24d4726b --- /dev/null +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmValueConsumers/SignatureAlgorithmValueConsumer.qll @@ -0,0 +1,38 @@ +import cpp +private import experimental.quantum.Language +private import experimental.quantum.OpenSSL.AlgorithmInstances.KnownAlgorithmConstants +private import experimental.quantum.OpenSSL.AlgorithmInstances.OpenSSLAlgorithmInstanceBase +private import OpenSSLAlgorithmValueConsumerBase + +abstract class SignatureAlgorithmValueConsumer extends OpenSSLAlgorithmValueConsumer { } + +class EVPSignatureAlgorithmValueConsumer extends OpenSSLAlgorithmValueConsumer { + DataFlow::Node valueArgNode; + DataFlow::Node resultNode; + Function consumer; + + EVPSignatureAlgorithmValueConsumer() { + resultNode.asExpr() = this and + this.(Call).getTarget() = consumer and + ( + // EVP_SIGNATURE + consumer.getName() = "EVP_SIGNATURE_fetch" and + valueArgNode.asExpr() = this.(Call).getArgument(1) + or + consumer.getName() = "EVP_SIGNATURE_is_a" and + valueArgNode.asExpr() = this.(Call).getArgument(1) + // EVP_PKEY_get1_DSA, DSA_SIG_new, EVP_RSA_gen + ) + } + + override DataFlow::Node getResultNode() { result = resultNode } + + override Crypto::ConsumerInputDataFlowNode getInputNode() { result = valueArgNode } + + // override DataFlow::Node getInputNode() { result = valueArgNode } + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { + exists(OpenSSLAlgorithmInstance i | i.getAVC() = this and result = i) + //TODO: As a potential alternative, for OpenSSL only, add a generic source node for literals and only create flow (flowsTo) to + // OpenSSL AVCs... the unknown literal sources would have to be any literals not in the known set. + } +} diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/ECKeyGenOperation.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/ECKeyGenOperation.qll index 4f07ecc0f9e3..b5ad6f9bd473 100644 --- a/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/ECKeyGenOperation.qll +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/ECKeyGenOperation.qll @@ -16,25 +16,15 @@ private module AlgGetterToAlgConsumerConfig implements DataFlow::ConfigSig { private module AlgGetterToAlgConsumerFlow = DataFlow::Global; -class ECKeyGenOperation extends OpenSSLOperation, Crypto::KeyGenerationOperationInstance { +class ECKeyGenOperation extends Crypto::KeyGenerationOperationInstance { ECKeyGenOperation() { this.(Call).getTarget().getName() = "EC_KEY_generate_key" } - override Expr getOutputArg() { - result = this.(Call) // return value of call - } - Expr getAlgorithmArg() { result = this.(Call).getArgument(0) } - override Expr getInputArg() { - // there is no 'input', in the sense that no data is being manipulated by the operation. - // There is an input of an algorithm, but that is not the intention of the operation input arg. - none() - } - override Crypto::KeyArtifactType getOutputKeyType() { result = Crypto::TAsymmetricKeyType() } override Crypto::ArtifactOutputDataFlowNode getOutputKeyArtifact() { - result = this.getOutputNode() + result.asExpr() = this.(Call) } override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPCipherInitializer.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPCipherInitializer.qll index 353a89645ec0..e6e9954a3332 100644 --- a/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPCipherInitializer.qll +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPCipherInitializer.qll @@ -5,6 +5,7 @@ private import experimental.quantum.Language private import experimental.quantum.OpenSSL.CtxFlow as CTXFlow +private import OpenSSLOperationBase module EncValToInitEncArgConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { source.asExpr().getValue().toInt() in [0, 1] } @@ -34,19 +35,12 @@ Crypto::KeyOperationSubtype intToCipherOperationSubtype(int i) { } // TODO: need to add key consumer -abstract class EVP_Cipher_Initializer extends Call { - Expr getContextArg() { result = this.(Call).getArgument(0) } +abstract class EVP_Cipher_Initializer extends EVPInitialize { + override Expr getAlgorithmArg() { result = this.(Call).getArgument(1) } - Expr getAlgorithmArg() { result = this.(Call).getArgument(1) } - - abstract Expr getKeyArg(); - - abstract Expr getIVArg(); - - // abstract Crypto::CipherOperationSubtype getCipherOperationSubtype(); abstract Expr getOperationSubtypeArg(); - Crypto::KeyOperationSubtype getCipherOperationSubtype() { + override Crypto::KeyOperationSubtype getKeyOperationSubtype() { if this.(Call).getTarget().getName().toLowerCase().matches("%encrypt%") then result instanceof Crypto::TEncryptMode else diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPCipherOperation.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPCipherOperation.qll index bb884f6db530..9477e5a1766a 100644 --- a/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPCipherOperation.qll +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPCipherOperation.qll @@ -4,34 +4,24 @@ private import EVPCipherInitializer private import OpenSSLOperationBase private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers -private module AlgGetterToAlgConsumerConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { - exists(OpenSSLAlgorithmValueConsumer c | c.getResultNode() = source) - } - predicate isSink(DataFlow::Node sink) { - exists(EVP_Cipher_Operation c | c.getInitCall().getAlgorithmArg() = sink.asExpr()) +class EVP_Cipher_Update_Call extends EVPUpdate { + EVP_Cipher_Update_Call() { + this.(Call).getTarget().getName() in [ + "EVP_EncryptUpdate", "EVP_DecryptUpdate", "EVP_CipherUpdate" + ] } -} -private module AlgGetterToAlgConsumerFlow = DataFlow::Global; + override Expr getInputArg() { result = this.(Call).getArgument(3) } +} -// import experimental.quantum.OpenSSL.AlgorithmValueConsumers.AlgorithmValueConsumers -// import OpenSSLOperation -// class EVPCipherOutput extends CipherOutputArtifact { -// EVPCipherOutput() { exists(EVP_Cipher_Operation op | op.getOutputArg() = this) } -// override DataFlow::Node getOutputNode() { result.asDefiningArgument() = this } -// } -// /** * see: https://docs.openssl.org/master/man3/EVP_EncryptInit/#synopsis * Base configuration for all EVP cipher operations. * NOTE: cannot extend instance of OpenSSLOperation, as we need to override * elements of OpenSSLOperation (i.e., we are creating an instance) */ -abstract class EVP_Cipher_Operation extends OpenSSLOperation, Crypto::KeyOperationInstance { - Expr getContextArg() { result = this.(Call).getArgument(0) } - +abstract class EVP_Cipher_Operation extends EVPOperation, Crypto::KeyOperationInstance { override Expr getOutputArg() { result = this.(Call).getArgument(1) } override Crypto::KeyOperationSubtype getKeyOperationSubtype() { @@ -41,81 +31,40 @@ abstract class EVP_Cipher_Operation extends OpenSSLOperation, Crypto::KeyOperati result instanceof Crypto::TDecryptMode and this.(Call).getTarget().getName().toLowerCase().matches("%decrypt%") or - result = this.getInitCall().getCipherOperationSubtype() and + result = this.getInitCall().getKeyOperationSubtype() and this.(Call).getTarget().getName().toLowerCase().matches("%cipher%") } - EVP_Cipher_Initializer getInitCall() { - CTXFlow::ctxArgFlowsToCtxArg(result.getContextArg(), this.getContextArg()) - } - override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { this.getInitCall().getIVArg() = result.asExpr() } - override Crypto::ConsumerInputDataFlowNode getInputConsumer() { result = this.getInputNode() } - override Crypto::ConsumerInputDataFlowNode getKeyConsumer() { this.getInitCall().getKeyArg() = result.asExpr() + // todo: or track to the EVP_PKEY_CTX_new } - override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { result = this.getOutputNode() } + override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { + result = this.(EVPOperation).getOutputArtifact() + } - override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { - AlgGetterToAlgConsumerFlow::flow(result.(OpenSSLAlgorithmValueConsumer).getResultNode(), - DataFlow::exprNode(this.getInitCall().getAlgorithmArg())) + override Crypto::ConsumerInputDataFlowNode getInputConsumer() { + result = this.(EVPOperation).getInputConsumer() } } -class EVP_Cipher_Call extends EVP_Cipher_Operation { +class EVP_Cipher_Call extends EVPOneShot, EVP_Cipher_Operation { EVP_Cipher_Call() { this.(Call).getTarget().getName() = "EVP_Cipher" } override Expr getInputArg() { result = this.(Call).getArgument(2) } } -// NOTE: not modeled as cipher operations, these are intermediate calls -class EVP_Cipher_Update_Call extends Call { - EVP_Cipher_Update_Call() { - this.(Call).getTarget().getName() in [ - "EVP_EncryptUpdate", "EVP_DecryptUpdate", "EVP_CipherUpdate" - ] - } - - Expr getInputArg() { result = this.(Call).getArgument(3) } - - DataFlow::Node getInputNode() { result.asExpr() = this.getInputArg() } - - Expr getContextArg() { result = this.(Call).getArgument(0) } -} -class EVP_Cipher_Final_Call extends EVP_Cipher_Operation { +class EVP_Cipher_Final_Call extends EVPFinal, EVP_Cipher_Operation { EVP_Cipher_Final_Call() { this.(Call).getTarget().getName() in [ "EVP_EncryptFinal_ex", "EVP_DecryptFinal_ex", "EVP_CipherFinal_ex", "EVP_EncryptFinal", "EVP_DecryptFinal", "EVP_CipherFinal" ] } - - EVP_Cipher_Update_Call getUpdateCalls() { - CTXFlow::ctxArgFlowsToCtxArg(result.getContextArg(), this.getContextArg()) - } - - override Expr getInputArg() { result = this.getUpdateCalls().getInputArg() } - - override Crypto::ConsumerInputDataFlowNode getInputConsumer() { result = this.getInputNode() } -} - -class EVP_PKEY_Operation extends EVP_Cipher_Operation { - EVP_PKEY_Operation() { - this.(Call).getTarget().getName() in ["EVP_PKEY_decrypt", "EVP_PKEY_encrypt"] - } - - override Expr getInputArg() { result = this.(Call).getArgument(3) } - // TODO: how PKEY is initialized is different that symmetric cipher - // Consider making an entirely new class for this and specializing - // the get init call -} - -class EVPCipherInputArgument extends Expr { - EVPCipherInputArgument() { exists(EVP_Cipher_Operation op | op.getInputArg() = this) } } diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPHashInitializer.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPHashInitializer.qll index 46d414ece6ce..672157d4865d 100644 --- a/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPHashInitializer.qll +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPHashInitializer.qll @@ -1,10 +1,7 @@ import cpp +private import OpenSSLOperationBase -abstract class EVP_Hash_Initializer extends Call { - Expr getContextArg() { result = this.(Call).getArgument(0) } - - abstract Expr getAlgorithmArg(); -} +abstract class EVP_Hash_Initializer extends EVPInitialize {} class EVP_DigestInit_Variant_Calls extends EVP_Hash_Initializer { EVP_DigestInit_Variant_Calls() { diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPHashOperation.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPHashOperation.qll index 43d10545357e..e58c362f9adf 100644 --- a/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPHashOperation.qll +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPHashOperation.qll @@ -8,73 +8,49 @@ private import OpenSSLOperationBase private import EVPHashInitializer private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers -// import EVPHashConsumers -abstract class EVP_Hash_Operation extends OpenSSLOperation, Crypto::HashOperationInstance { - Expr getContextArg() { result = this.(Call).getArgument(0) } - EVP_Hash_Initializer getInitCall() { - CTXFlow::ctxArgFlowsToCtxArg(result.getContextArg(), this.getContextArg()) - } - - /** - * By default, the algorithm value comes from the init call. - * There are variants where this isn't true, in which case the - * subclass should override this method. - */ - override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { - AlgGetterToAlgConsumerFlow::flow(result.(OpenSSLAlgorithmValueConsumer).getResultNode(), - DataFlow::exprNode(this.getInitCall().getAlgorithmArg())) - } -} - -private module AlgGetterToAlgConsumerConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { - exists(OpenSSLAlgorithmValueConsumer c | c.getResultNode() = source) - } +class EVP_Digest_Update_Call extends EVPUpdate { + EVP_Digest_Update_Call() { this.(Call).getTarget().getName() in ["EVP_DigestUpdate"] } - predicate isSink(DataFlow::Node sink) { - exists(EVP_Hash_Operation c | c.getInitCall().getAlgorithmArg() = sink.asExpr()) - } + override Expr getInputArg() { result = this.(Call).getArgument(1) } } -private module AlgGetterToAlgConsumerFlow = DataFlow::Global; //https://docs.openssl.org/3.0/man3/EVP_DigestInit/#synopsis -class EVP_Q_Digest_Operation extends EVP_Hash_Operation { +class EVP_Q_Digest_Operation extends EVPOneShot, Crypto::HashOperationInstance { EVP_Q_Digest_Operation() { this.(Call).getTarget().getName() = "EVP_Q_digest" } - //override Crypto::AlgorithmConsumer getAlgorithmConsumer() { } + override Expr getAlgorithmArg() { + result = this.(Call).getArgument(1) + } + override EVP_Hash_Initializer getInitCall() { // This variant of digest does not use an init // and even if it were used, the init would be ignored/undefined none() } - override Expr getOutputArg() { result = this.(Call).getArgument(5) } - override Expr getInputArg() { result = this.(Call).getArgument(3) } - override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { result = this.getOutputNode() } + override Expr getOutputArg() { result = this.(Call).getArgument(5) } - override Crypto::ConsumerInputDataFlowNode getInputConsumer() { result = this.getInputNode() } + override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { + result = this.(EVPOperation).getOutputArtifact() + } - override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { - // The operation is a direct algorithm consumer - // NOTE: the operation itself is already modeld as a value consumer, so we can - // simply return 'this', see modeled hash algorithm consuers for EVP_Q_Digest - this = result + override Crypto::ConsumerInputDataFlowNode getInputConsumer() { + result = this.(EVPOperation).getInputConsumer() } } -class EVP_Digest_Operation extends EVP_Hash_Operation { +class EVP_Digest_Operation extends EVPOneShot, Crypto::HashOperationInstance { EVP_Digest_Operation() { this.(Call).getTarget().getName() = "EVP_Digest" } // There is no context argument for this function override Expr getContextArg() { none() } - override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { - AlgGetterToAlgConsumerFlow::flow(result.(OpenSSLAlgorithmValueConsumer).getResultNode(), - DataFlow::exprNode(this.(Call).getArgument(4))) + override Expr getAlgorithmArg() { + result = this.(Call).getArgument(4) } override EVP_Hash_Initializer getInitCall() { @@ -83,42 +59,33 @@ class EVP_Digest_Operation extends EVP_Hash_Operation { none() } - override Expr getOutputArg() { result = this.(Call).getArgument(2) } - override Expr getInputArg() { result = this.(Call).getArgument(0) } - override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { result = this.getOutputNode() } - - override Crypto::ConsumerInputDataFlowNode getInputConsumer() { result = this.getInputNode() } -} - -// NOTE: not modeled as hash operations, these are intermediate calls -class EVP_Digest_Update_Call extends Call { - EVP_Digest_Update_Call() { this.(Call).getTarget().getName() in ["EVP_DigestUpdate"] } - - Expr getInputArg() { result = this.(Call).getArgument(1) } + override Expr getOutputArg() { result = this.(Call).getArgument(2) } - DataFlow::Node getInputNode() { result.asExpr() = this.getInputArg() } + override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { + result = this.(EVPOperation).getOutputArtifact() + } - Expr getContextArg() { result = this.(Call).getArgument(0) } + override Crypto::ConsumerInputDataFlowNode getInputConsumer() { + result = this.(EVPOperation).getInputConsumer() + } } -class EVP_Digest_Final_Call extends EVP_Hash_Operation { +class EVP_Digest_Final_Call extends EVPFinal, Crypto::HashOperationInstance { EVP_Digest_Final_Call() { this.(Call).getTarget().getName() in [ "EVP_DigestFinal", "EVP_DigestFinal_ex", "EVP_DigestFinalXOF" ] } - EVP_Digest_Update_Call getUpdateCalls() { - CTXFlow::ctxArgFlowsToCtxArg(result.getContextArg(), this.getContextArg()) - } - - override Expr getInputArg() { result = this.getUpdateCalls().getInputArg() } - - override Crypto::ConsumerInputDataFlowNode getInputConsumer() { result = this.getInputNode() } - override Expr getOutputArg() { result = this.(Call).getArgument(1) } + + override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { + result = this.(EVPOperation).getOutputArtifact() + } - override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { result = this.getOutputNode() } + override Crypto::ConsumerInputDataFlowNode getInputConsumer() { + result = this.(EVPOperation).getInputConsumer() + } } diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPSignatureOperation.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPSignatureOperation.qll new file mode 100644 index 000000000000..01dcb44f05b3 --- /dev/null +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPSignatureOperation.qll @@ -0,0 +1,53 @@ +/** + * Provides classes for modeling OpenSSL's EVP signature operations + */ + +private import experimental.quantum.Language +private import OpenSSLOperationBase +private import experimental.quantum.OpenSSL.CtxFlow as CTXFlow +private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumerBase +private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers + +// TODO: verification + +abstract class EVP_Cipher_Initializer extends EVPInitialize { + Expr keyArg; + Expr algorithmArg; + + EVP_Cipher_Initializer() { + this.(Call).getTarget().getName() in [ + "EVP_DigestSignInit", "EVP_DigestSignInit_ex", + "EVP_SignInit", "EVP_SignInit_ex", + "EVP_PKEY_sign_init", "EVP_PKEY_sign_init_ex", "EVP_PKEY_sign_init_ex2", "EVP_PKEY_sign_message_init" + ] and + ( + this.(Call).getTarget().getName() = "EVP_DigestSignInit" and + keyArg = this.(Call).getArgument(5) + or + this.(Call).getTarget().getName() = "EVP_DigestSignInit_ex" and + keyArg = this.(Call).getArgument(4) + or + this.(Call).getTarget().getName() = "EVP_PKEY_sign_init_ex2" and + algorithmArg = this.(Call).getArgument(1) + or + this.(Call).getTarget().getName() = "EVP_PKEY_sign_message_init" and + algorithmArg = this.(Call).getArgument(1) + ) + } + + override Expr getAlgorithmArg() { result = algorithmArg } + + override Expr getKeyArg() { result = keyArg } + + override Expr getIVArg() { none() } + + override Crypto::KeyOperationSubtype getKeyOperationSubtype() { + if this.(Call).getTarget().getName().toLowerCase().matches("%sign%") + then result instanceof Crypto::TSignMode + else + if this.(Call).getTarget().getName().toLowerCase().matches("%verify%") + then result instanceof Crypto::TVerifyMode + else + result instanceof Crypto::TUnknownKeyOperationMode + } +} diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/OpenSSLOperationBase.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/OpenSSLOperationBase.qll index f9753e92c5d2..ac4ba969c87a 100644 --- a/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/OpenSSLOperationBase.qll +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/OpenSSLOperationBase.qll @@ -1,21 +1,107 @@ private import experimental.quantum.Language +private import experimental.quantum.OpenSSL.CtxFlow as CTXFlow +private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers -abstract class OpenSSLOperation extends Crypto::OperationInstance instanceof Call { + +abstract class EVPInitialize extends Call { + Expr getContextArg() { result = this.(Call).getArgument(0) } + + Crypto::KeyOperationSubtype getKeyOperationSubtype() { + none() + } + + /** + * Explicitly specified algorithm or none if implicit (e.g., established by the key). + */ + Expr getAlgorithmArg() { none() } + + Expr getKeyArg() { none() } + + Expr getIVArg() { none() } +} + +abstract class EVPUpdate extends Call { + Expr getContextArg() { result = this.(Call).getArgument(0) } + + /** + * Update has some input data like plaintext or message digest. + */ + abstract Expr getInputArg(); +} + +/** + * Flows from algorithm values to operations, specific to OpenSSL + */ +private module AlgGetterToAlgConsumerConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + exists(OpenSSLAlgorithmValueConsumer c | c.getResultNode() = source) + } + + predicate isSink(DataFlow::Node sink) { + exists(EVPOperation c | c.getInitCall().getAlgorithmArg() = sink.asExpr()) + } +} + +private module AlgGetterToAlgConsumerFlow = DataFlow::Global; + + +abstract class EVPOperation extends Crypto::OperationInstance { + Expr getContextArg() { result = this.(Call).getArgument(0) } + + /** + * Some input data like plaintext or message digest. + * Either argument provided direcly in the call or all arguments that were provided in update calls. + */ abstract Expr getInputArg(); /** - * Can be an argument of a call or a return value of a function. + * Some output data like ciphertext or signature. + * Always produced directly by this operation. + * Assumption: output is provided as an argument to the call, never as return value. */ abstract Expr getOutputArg(); - DataFlow::Node getInputNode() { - // Assumed to be default to asExpr - result.asExpr() = this.getInputArg() + /** + * Explicitly specified algorithm or leave base implementation to find it in an init call. + */ + Expr getAlgorithmArg() { + if exists(this.getInitCall()) then result = this.getInitCall().getAlgorithmArg() + else none() } - DataFlow::Node getOutputNode() { - if exists(Call c | c.getAnArgument() = this) - then result.asDefiningArgument() = this - else result.asExpr() = this + /** + * Finds the initialization call, may be none. + */ + EVPInitialize getInitCall() { + CTXFlow::ctxArgFlowsToCtxArg(result.getContextArg(), this.getContextArg()) } + + /** + * Algorithm was specified in either init call or is implicitly established by the key. + */ + override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { + AlgGetterToAlgConsumerFlow::flow(result.(OpenSSLAlgorithmValueConsumer).getResultNode(), + DataFlow::exprNode(this.getAlgorithmArg())) + } + + Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { + result.asExpr() = this.getOutputArg() + // if exists(Call c | c.getAnArgument() = this) + // then result.asDefiningArgument() = this + // else result.asExpr() = this + } + + Crypto::ConsumerInputDataFlowNode getInputConsumer() { result.asExpr() = this.getInputArg() } } + +abstract class EVPFinal extends EVPOperation { + EVPUpdate getUpdateCalls() { + CTXFlow::ctxArgFlowsToCtxArg(result.getContextArg(), this.getContextArg()) + } + + override Expr getInputArg() { result = this.getUpdateCalls().getInputArg() } +} + +abstract class EVPOneShot extends EVPOperation { + +} \ No newline at end of file diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/OpenSSLOperations.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/OpenSSLOperations.qll index f6ff0dd1f077..54ac977ead0d 100644 --- a/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/OpenSSLOperations.qll +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/Operations/OpenSSLOperations.qll @@ -2,3 +2,4 @@ import OpenSSLOperationBase import EVPCipherOperation import EVPHashOperation import ECKeyGenOperation +import EVPSignatureOperation diff --git a/cpp/ql/test/library-tests/quantum/openssl/openssl_cipher.c b/cpp/ql/test/library-tests/quantum/openssl/openssl_cipher.c new file mode 100644 index 000000000000..fe1aec464edd --- /dev/null +++ b/cpp/ql/test/library-tests/quantum/openssl/openssl_cipher.c @@ -0,0 +1,221 @@ +#include +#include +#include +#include +#include + +// Sample OpenSSL code that demonstrates various cryptographic operations +// that can be detected by the quantum model + +// Function to perform AES-256-GCM encryption +int encrypt_aes_gcm(const unsigned char *plaintext, int plaintext_len, + const unsigned char *key, const unsigned char *iv, int iv_len, + unsigned char *ciphertext, unsigned char *tag) { + EVP_CIPHER_CTX *ctx; + int len; + int ciphertext_len; + + // Create and initialize the context + if(!(ctx = EVP_CIPHER_CTX_new())) + return -1; + + // Initialize the encryption operation + if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)) + return -1; + + // Set IV length (for GCM mode) + if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL)) + return -1; + + // Initialize key and IV + if(1 != EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv)) + return -1; + + // Provide the plaintext to be encrypted + if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)) + return -1; + ciphertext_len = len; + + // Finalize the encryption + if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) + return -1; + ciphertext_len += len; + + // Get the tag + if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag)) + return -1; + + // Clean up + EVP_CIPHER_CTX_free(ctx); + + return ciphertext_len; +} + +// Function to perform AES-256-GCM decryption +int decrypt_aes_gcm(const unsigned char *ciphertext, int ciphertext_len, + const unsigned char *tag, const unsigned char *key, + const unsigned char *iv, int iv_len, + unsigned char *plaintext) { + EVP_CIPHER_CTX *ctx; + int len; + int plaintext_len; + int ret; + + // Create and initialize the context + if(!(ctx = EVP_CIPHER_CTX_new())) + return -1; + + // Initialize the decryption operation + if(!EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)) + return -1; + + // Set IV length + if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL)) + return -1; + + // Initialize key and IV + if(!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) + return -1; + + // Provide the ciphertext to be decrypted + if(!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) + return -1; + plaintext_len = len; + + // Set expected tag value + if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (void*)tag)) + return -1; + + // Finalize the decryption + ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len); + + // Clean up + EVP_CIPHER_CTX_free(ctx); + + if(ret > 0) { + // Success + plaintext_len += len; + return plaintext_len; + } else { + // Verification failed + return -1; + } +} + +// Function to calculate SHA-256 hash +int calculate_sha256(const unsigned char *message, size_t message_len, + unsigned char *digest) { + EVP_MD_CTX *mdctx; + unsigned int digest_len; + + // Create and initialize the context + if(!(mdctx = EVP_MD_CTX_new())) + return 0; + + // Initialize the hash operation + if(1 != EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL)) + return 0; + + // Provide the message to be hashed + if(1 != EVP_DigestUpdate(mdctx, message, message_len)) + return 0; + + // Finalize the hash + if(1 != EVP_DigestFinal_ex(mdctx, digest, &digest_len)) + return 0; + + // Clean up + EVP_MD_CTX_free(mdctx); + + return 1; +} + +// Function to generate random bytes +int generate_random_bytes(unsigned char *buffer, size_t length) { + return RAND_bytes(buffer, length); +} + +// Function using direct EVP_Digest function (one-shot hash) +int calculate_md5_oneshot(const unsigned char *message, size_t message_len, + unsigned char *digest) { + unsigned int digest_len; + + // Calculate MD5 in a single call + if(1 != EVP_Digest(message, message_len, digest, &digest_len, EVP_md5(), NULL)) + return 0; + + return 1; +} + +// Function using HMAC +int calculate_hmac_sha256(const unsigned char *key, size_t key_len, + const unsigned char *message, size_t message_len, + unsigned char *mac) { + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + EVP_PKEY *pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, key, key_len); + + if (!ctx || !pkey) + return 0; + + if (EVP_DigestSignInit(ctx, NULL, EVP_sha256(), NULL, pkey) != 1) + return 0; + + if (EVP_DigestSignUpdate(ctx, message, message_len) != 1) + return 0; + + size_t mac_len = 32; // SHA-256 output size + if (EVP_DigestSignFinal(ctx, mac, &mac_len) != 1) + return 0; + + EVP_MD_CTX_free(ctx); + EVP_PKEY_free(pkey); + + return 1; +} + +// Test function +int main() { + // Test encryption and decryption + unsigned char *key = (unsigned char *)"01234567890123456789012345678901"; // 32 bytes + unsigned char *iv = (unsigned char *)"0123456789012345"; // 16 bytes + unsigned char *plaintext = (unsigned char *)"This is a test message for encryption"; + unsigned char ciphertext[1024]; + unsigned char tag[16]; + unsigned char decrypted[1024]; + int plaintext_len = strlen((char *)plaintext); + int ciphertext_len; + int decrypted_len; + + // Test SHA-256 hash + unsigned char hash[32]; + + // Test random generation + unsigned char random_bytes[32]; + + // Initialize OpenSSL + ERR_load_crypto_strings(); + + // Encrypt data + ciphertext_len = encrypt_aes_gcm(plaintext, plaintext_len, key, iv, 16, ciphertext, tag); + + // Decrypt data + decrypted_len = decrypt_aes_gcm(ciphertext, ciphertext_len, tag, key, iv, 16, decrypted); + + printf("decrypted: %s\n", decrypted); + + // Calculate hash + calculate_sha256(plaintext, plaintext_len, hash); + + // Generate random bytes + generate_random_bytes(random_bytes, 32); + + // Calculate one-shot MD5 + unsigned char md5_hash[16]; + calculate_md5_oneshot(plaintext, plaintext_len, md5_hash); + + // Calculate HMAC + unsigned char hmac[32]; + calculate_hmac_sha256(key, 32, plaintext, plaintext_len, hmac); + + return 0; +} \ No newline at end of file diff --git a/cpp/ql/test/library-tests/quantum/openssl/openssl_signature.c b/cpp/ql/test/library-tests/quantum/openssl/openssl_signature.c new file mode 100644 index 000000000000..c174a82441cc --- /dev/null +++ b/cpp/ql/test/library-tests/quantum/openssl/openssl_signature.c @@ -0,0 +1,1173 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Helper function to print OpenSSL errors */ +void print_openssl_errors(void) { + unsigned long err; + char err_buf[256]; + + while ((err = ERR_get_error()) != 0) { + ERR_error_string_n(err, err_buf, sizeof(err_buf)); + fprintf(stderr, "OpenSSL error: %s\n", err_buf); + } +} + +/* + * Pair 1: Using EVP_Sign API (older API) + * This API is simpler but less flexible + */ + +/* Sign a message using EVP_Sign API */ +int sign_using_evp_sign(const unsigned char *message, size_t message_len, + unsigned char **signature, size_t *signature_len, + EVP_PKEY *pkey, const EVP_MD *md) { + EVP_MD_CTX *md_ctx = NULL; + int ret = 0; + unsigned int sig_len = 0; + + /* Create digest context */ + if (!(md_ctx = EVP_MD_CTX_new())) { + fprintf(stderr, "Failed to create EVP_MD_CTX\n"); + print_openssl_errors(); + return 0; + } + + /* Initialize digest operation */ + if (EVP_SignInit(md_ctx, md) != 1) { + fprintf(stderr, "Failed to initialize signing operation\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Process the message */ + if (EVP_SignUpdate(md_ctx, message, message_len) != 1) { + fprintf(stderr, "Failed to update signing operation\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Allocate memory for signature */ + *signature_len = EVP_PKEY_size(pkey); + *signature = OPENSSL_malloc(*signature_len); + if (!(*signature)) { + fprintf(stderr, "Failed to allocate memory for signature\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Generate signature */ + if (EVP_SignFinal(md_ctx, *signature, &sig_len, pkey) != 1) { + fprintf(stderr, "Failed to create signature\n"); + print_openssl_errors(); + OPENSSL_free(*signature); + *signature = NULL; + goto cleanup; + } + + *signature_len = sig_len; + ret = 1; + +cleanup: + EVP_MD_CTX_free(md_ctx); + return ret; +} + +/* Verify a signature using EVP_Verify API */ +int verify_using_evp_verify(const unsigned char *message, size_t message_len, + const unsigned char *signature, size_t signature_len, + EVP_PKEY *pkey, const EVP_MD *md) { + EVP_MD_CTX *md_ctx = NULL; + int ret = 0; + + /* Create digest context */ + if (!(md_ctx = EVP_MD_CTX_new())) { + fprintf(stderr, "Failed to create EVP_MD_CTX\n"); + print_openssl_errors(); + return 0; + } + + /* Initialize digest operation */ + if (EVP_VerifyInit(md_ctx, md) != 1) { + fprintf(stderr, "Failed to initialize verification operation\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Process the message */ + if (EVP_VerifyUpdate(md_ctx, message, message_len) != 1) { + fprintf(stderr, "Failed to update verification operation\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Verify the signature */ + if (EVP_VerifyFinal(md_ctx, signature, (unsigned int)signature_len, pkey) != 1) { + fprintf(stderr, "Signature verification failed\n"); + print_openssl_errors(); + goto cleanup; + } + + ret = 1; + +cleanup: + EVP_MD_CTX_free(md_ctx); + return ret; +} + +/* + * Pair 2: Using EVP_DigestSign API (recommended modern API) + * This API is more flexible and supports multiple algorithms + */ + +/* Sign a message using EVP_DigestSign API */ +int sign_using_evp_digestsign(const unsigned char *message, size_t message_len, + unsigned char **signature, size_t *signature_len, + EVP_PKEY *pkey, const EVP_MD *md) { + EVP_MD_CTX *md_ctx = NULL; + int ret = 0; + + /* Create digest context */ + if (!(md_ctx = EVP_MD_CTX_new())) { + fprintf(stderr, "Failed to create EVP_MD_CTX\n"); + print_openssl_errors(); + return 0; + } + + /* Initialize the DigestSign operation */ + if (EVP_DigestSignInit(md_ctx, NULL, md, NULL, pkey) != 1) { + fprintf(stderr, "Failed to initialize DigestSign operation\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Process the message */ + if (EVP_DigestSignUpdate(md_ctx, message, message_len) != 1) { + fprintf(stderr, "Failed to update DigestSign operation\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Get signature length */ + if (EVP_DigestSignFinal(md_ctx, NULL, signature_len) != 1) { + fprintf(stderr, "Failed to determine signature length\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Allocate memory for signature */ + *signature = OPENSSL_malloc(*signature_len); + if (!(*signature)) { + fprintf(stderr, "Failed to allocate memory for signature\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Generate signature */ + if (EVP_DigestSignFinal(md_ctx, *signature, signature_len) != 1) { + fprintf(stderr, "Failed to create signature\n"); + print_openssl_errors(); + OPENSSL_free(*signature); + *signature = NULL; + goto cleanup; + } + + ret = 1; + +cleanup: + EVP_MD_CTX_free(md_ctx); + return ret; +} + +/* Verify a signature using EVP_DigestVerify API */ +int verify_using_evp_digestverify(const unsigned char *message, size_t message_len, + const unsigned char *signature, size_t signature_len, + EVP_PKEY *pkey, const EVP_MD *md) { + EVP_MD_CTX *md_ctx = NULL; + int ret = 0; + + /* Create digest context */ + if (!(md_ctx = EVP_MD_CTX_new())) { + fprintf(stderr, "Failed to create EVP_MD_CTX\n"); + print_openssl_errors(); + return 0; + } + + /* Initialize the DigestVerify operation */ + if (EVP_DigestVerifyInit(md_ctx, NULL, md, NULL, pkey) != 1) { + fprintf(stderr, "Failed to initialize DigestVerify operation\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Process the message */ + if (EVP_DigestVerifyUpdate(md_ctx, message, message_len) != 1) { + fprintf(stderr, "Failed to update DigestVerify operation\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Verify signature */ + if (EVP_DigestVerifyFinal(md_ctx, signature, signature_len) != 1) { + fprintf(stderr, "Signature verification failed\n"); + print_openssl_errors(); + goto cleanup; + } + + ret = 1; + +cleanup: + EVP_MD_CTX_free(md_ctx); + return ret; +} + +/* + * Pair 3: Using EVP_PKEY_sign API (lower level API with direct control) + * This API offers direct control over the signing operation + */ + +/* Sign a message using EVP_PKEY_sign API - requires pre-hashed message */ +int sign_using_evp_pkey_sign(const unsigned char *digest, size_t digest_len, + unsigned char **signature, size_t *signature_len, + EVP_PKEY *pkey, const EVP_MD *md) { + EVP_PKEY_CTX *pkey_ctx = NULL; + int ret = 0; + + /* Create the context for the signing operation */ + if (!(pkey_ctx = EVP_PKEY_CTX_new(pkey, NULL))) { + fprintf(stderr, "Failed to create PKEY context\n"); + print_openssl_errors(); + return 0; + } + + /* Initialize signing operation */ + if (EVP_PKEY_sign_init(pkey_ctx) != 1) { + fprintf(stderr, "Failed to initialize signing operation\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Set the message digest to use */ + if (EVP_PKEY_CTX_set_signature_md(pkey_ctx, md) != 1) { + fprintf(stderr, "Failed to set signature message digest\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Get signature length */ + if (EVP_PKEY_sign(pkey_ctx, NULL, signature_len, digest, digest_len) != 1) { + fprintf(stderr, "Failed to determine signature length\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Allocate memory for signature */ + *signature = OPENSSL_malloc(*signature_len); + if (!(*signature)) { + fprintf(stderr, "Failed to allocate memory for signature\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Generate signature */ + if (EVP_PKEY_sign(pkey_ctx, *signature, signature_len, digest, digest_len) != 1) { + fprintf(stderr, "Failed to create signature\n"); + print_openssl_errors(); + OPENSSL_free(*signature); + *signature = NULL; + goto cleanup; + } + + ret = 1; + +cleanup: + EVP_PKEY_CTX_free(pkey_ctx); + return ret; +} + +/* Verify a signature using EVP_PKEY_verify API - requires pre-hashed message */ +int verify_using_evp_pkey_verify(const unsigned char *digest, size_t digest_len, + const unsigned char *signature, size_t signature_len, + EVP_PKEY *pkey, const EVP_MD *md) { + EVP_PKEY_CTX *pkey_ctx = NULL; + int ret = 0; + + /* Create the context for verification operation */ + if (!(pkey_ctx = EVP_PKEY_CTX_new(pkey, NULL))) { + fprintf(stderr, "Failed to create PKEY context\n"); + print_openssl_errors(); + return 0; + } + + /* Initialize verification operation */ + if (EVP_PKEY_verify_init(pkey_ctx) != 1) { + fprintf(stderr, "Failed to initialize verification operation\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Set the message digest */ + if (EVP_PKEY_CTX_set_signature_md(pkey_ctx, md) != 1) { + fprintf(stderr, "Failed to set signature message digest\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Verify signature */ + if (EVP_PKEY_verify(pkey_ctx, signature, signature_len, digest, digest_len) != 1) { + fprintf(stderr, "Signature verification failed\n"); + print_openssl_errors(); + goto cleanup; + } + + ret = 1; + +cleanup: + EVP_PKEY_CTX_free(pkey_ctx); + return ret; +} + +/* + * Pair 4: Using EVP_MD_CTX with EVP_PKEY_CTX (fine-grained control) + * This allows for customization of the signing parameters + */ + +/* Sign a message using EVP_DigestSignInit with explicit EVP_PKEY_CTX */ +int sign_using_digestsign_with_ctx(const unsigned char *message, size_t message_len, + unsigned char **signature, size_t *signature_len, + EVP_PKEY *pkey, const EVP_MD *md, + int (*param_setter)(EVP_PKEY_CTX *ctx)) { + EVP_MD_CTX *md_ctx = NULL; + EVP_PKEY_CTX *pkey_ctx = NULL; + int ret = 0; + + /* Create digest context */ + if (!(md_ctx = EVP_MD_CTX_new())) { + fprintf(stderr, "Failed to create digest context\n"); + print_openssl_errors(); + return 0; + } + + /* Initialize the DigestSign operation with explicit PKEY_CTX */ + if (EVP_DigestSignInit(md_ctx, &pkey_ctx, md, NULL, pkey) != 1) { + fprintf(stderr, "Failed to initialize DigestSign operation\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Apply custom parameters if provided */ + if (param_setter && param_setter(pkey_ctx) != 1) { + fprintf(stderr, "Failed to set custom parameters\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Process the message */ + if (EVP_DigestSignUpdate(md_ctx, message, message_len) != 1) { + fprintf(stderr, "Failed to update DigestSign operation\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Get signature length */ + if (EVP_DigestSignFinal(md_ctx, NULL, signature_len) != 1) { + fprintf(stderr, "Failed to determine signature length\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Allocate memory for signature */ + *signature = OPENSSL_malloc(*signature_len); + if (!(*signature)) { + fprintf(stderr, "Failed to allocate memory for signature\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Generate signature */ + if (EVP_DigestSignFinal(md_ctx, *signature, signature_len) != 1) { + fprintf(stderr, "Failed to create signature\n"); + print_openssl_errors(); + OPENSSL_free(*signature); + *signature = NULL; + goto cleanup; + } + + ret = 1; + +cleanup: + EVP_MD_CTX_free(md_ctx); + return ret; +} + +/* Verify a signature using EVP_DigestVerifyInit with explicit EVP_PKEY_CTX */ +int verify_using_digestverify_with_ctx(const unsigned char *message, size_t message_len, + const unsigned char *signature, size_t signature_len, + EVP_PKEY *pkey, const EVP_MD *md, + int (*param_setter)(EVP_PKEY_CTX *ctx)) { + EVP_MD_CTX *md_ctx = NULL; + EVP_PKEY_CTX *pkey_ctx = NULL; + int ret = 0; + + /* Create digest context */ + if (!(md_ctx = EVP_MD_CTX_new())) { + fprintf(stderr, "Failed to create digest context\n"); + print_openssl_errors(); + return 0; + } + + /* Initialize the DigestVerify operation with explicit PKEY_CTX */ + if (EVP_DigestVerifyInit(md_ctx, &pkey_ctx, md, NULL, pkey) != 1) { + fprintf(stderr, "Failed to initialize DigestVerify operation\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Apply custom parameters if provided */ + if (param_setter && param_setter(pkey_ctx) != 1) { + fprintf(stderr, "Failed to set custom parameters\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Process the message */ + if (EVP_DigestVerifyUpdate(md_ctx, message, message_len) != 1) { + fprintf(stderr, "Failed to update DigestVerify operation\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Verify signature */ + if (EVP_DigestVerifyFinal(md_ctx, signature, signature_len) != 1) { + fprintf(stderr, "Signature verification failed\n"); + print_openssl_errors(); + goto cleanup; + } + + ret = 1; + +cleanup: + EVP_MD_CTX_free(md_ctx); + return ret; +} + +/* + * Pair 5: Using the old low-level APIs (for compatibility with legacy code) + * These APIs are deprecated but may still be found in older codebases + */ + +/* Sign using RSA_sign (low-level API, only for RSA) */ +int sign_using_rsa_sign(const unsigned char *message, size_t message_len, + unsigned char **signature, size_t *signature_len, + RSA *rsa_key, int hash_nid, const EVP_MD *md) { + unsigned char digest[EVP_MAX_MD_SIZE]; + unsigned int digest_len; + int ret = 0; + EVP_MD_CTX *md_ctx = NULL; + + /* Create digest context */ + if (!(md_ctx = EVP_MD_CTX_new())) { + fprintf(stderr, "Failed to create EVP_MD_CTX\n"); + print_openssl_errors(); + return 0; + } + + /* Calculate hash of the message */ + if (EVP_DigestInit_ex(md_ctx, md, NULL) != 1) { + fprintf(stderr, "Failed to initialize digest\n"); + print_openssl_errors(); + goto cleanup; + } + + if (EVP_DigestUpdate(md_ctx, message, message_len) != 1) { + fprintf(stderr, "Failed to update digest\n"); + print_openssl_errors(); + goto cleanup; + } + + if (EVP_DigestFinal_ex(md_ctx, digest, &digest_len) != 1) { + fprintf(stderr, "Failed to finalize digest\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Allocate memory for signature */ + *signature_len = RSA_size(rsa_key); + *signature = OPENSSL_malloc(*signature_len); + if (!(*signature)) { + fprintf(stderr, "Failed to allocate memory for signature\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Sign with RSA_sign */ + if (RSA_sign(hash_nid, digest, digest_len, *signature, + (unsigned int*)signature_len, rsa_key) != 1) { + fprintf(stderr, "Failed to create RSA signature\n"); + print_openssl_errors(); + OPENSSL_free(*signature); + *signature = NULL; + goto cleanup; + } + + ret = 1; + +cleanup: + EVP_MD_CTX_free(md_ctx); + return ret; +} + +/* Verify using RSA_verify (low-level API, only for RSA) */ +int verify_using_rsa_verify(const unsigned char *message, size_t message_len, + const unsigned char *signature, size_t signature_len, + RSA *rsa_key, int hash_nid, const EVP_MD *md) { + unsigned char digest[EVP_MAX_MD_SIZE]; + unsigned int digest_len; + int ret = 0; + EVP_MD_CTX *md_ctx = NULL; + + /* Create digest context */ + if (!(md_ctx = EVP_MD_CTX_new())) { + fprintf(stderr, "Failed to create EVP_MD_CTX\n"); + print_openssl_errors(); + return 0; + } + + /* Calculate hash of the message */ + if (EVP_DigestInit_ex(md_ctx, md, NULL) != 1) { + fprintf(stderr, "Failed to initialize digest\n"); + print_openssl_errors(); + goto cleanup; + } + + if (EVP_DigestUpdate(md_ctx, message, message_len) != 1) { + fprintf(stderr, "Failed to update digest\n"); + print_openssl_errors(); + goto cleanup; + } + + if (EVP_DigestFinal_ex(md_ctx, digest, &digest_len) != 1) { + fprintf(stderr, "Failed to finalize digest\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Verify signature with RSA_verify */ + if (RSA_verify(hash_nid, digest, digest_len, signature, + (unsigned int)signature_len, rsa_key) != 1) { + fprintf(stderr, "RSA signature verification failed\n"); + print_openssl_errors(); + goto cleanup; + } + + ret = 1; + +cleanup: + EVP_MD_CTX_free(md_ctx); + return ret; +} + +/* + * Helper function example for getting an RSA key from an EVP_PKEY + * (would be needed for the low-level RSA API functions) + */ +RSA *get_rsa_from_pkey(EVP_PKEY *pkey) { + RSA *rsa = EVP_PKEY_get1_RSA(pkey); + if (!rsa) { + fprintf(stderr, "Failed to get RSA key from EVP_PKEY\n"); + print_openssl_errors(); + } + return rsa; +} + +/* Helper function for setting RSA padding mode to PSS */ +int set_rsa_pss_padding(EVP_PKEY_CTX *ctx) { + /* Set padding mode to PSS */ + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING) != 1) { + fprintf(stderr, "Failed to set RSA padding to PSS\n"); + print_openssl_errors(); + return 0; + } + + return 1; +} + +/* Generic test function demonstrating APIs 1-4 with any algorithm */ +int test_signature_apis(EVP_PKEY *key, const EVP_MD *md, + int (*param_setter)(EVP_PKEY_CTX *ctx), + const char *algo_name) { + unsigned char *signature1 = NULL, *signature2 = NULL, + *signature3 = NULL, *signature4 = NULL; + size_t sig_len1 = 0, sig_len2 = 0, sig_len3 = 0, sig_len4 = 0; + int result = 0; + + /* Message to sign */ + const unsigned char message[] = "Test message for OpenSSL signature APIs"; + const size_t message_len = strlen((char *)message); + + printf("\nTesting signature APIs with %s:\n", algo_name); + + /* Test Pair 1: EVP_Sign API */ + printf("1. Testing EVP_Sign API (older API):\n"); + if (sign_using_evp_sign(message, message_len, &signature1, &sig_len1, key, md)) { + printf(" Signature created successfully (length: %zu bytes)\n", sig_len1); + + /* Verify the signature */ + if (verify_using_evp_verify(message, message_len, signature1, sig_len1, key, md)) { + printf(" Signature verified successfully\n"); + } else { + printf(" Signature verification failed\n"); + goto cleanup; + } + } else { + printf(" Failed to create signature\n"); + goto cleanup; + } + + /* Test Pair 2: EVP_DigestSign API */ + printf("\n2. Testing EVP_DigestSign API (recommended modern API):\n"); + if (sign_using_evp_digestsign(message, message_len, &signature2, &sig_len2, key, md)) { + printf(" Signature created successfully (length: %zu bytes)\n", sig_len2); + + /* Verify the signature */ + if (verify_using_evp_digestverify(message, message_len, signature2, sig_len2, key, md)) { + printf(" Signature verified successfully\n"); + } else { + printf(" Signature verification failed\n"); + goto cleanup; + } + } else { + printf(" Failed to create signature\n"); + goto cleanup; + } + + /* Test Pair 3: EVP_PKEY_sign API */ + printf("\n3. Testing EVP_PKEY_sign API (lower level API):\n"); + /* First create a digest of the message */ + unsigned char digest[EVP_MAX_MD_SIZE]; + unsigned int digest_len; + EVP_MD_CTX *md_ctx = EVP_MD_CTX_new(); + if (!md_ctx) { + fprintf(stderr, "Failed to create digest context\n"); + print_openssl_errors(); + goto cleanup; + } + + if (EVP_DigestInit_ex(md_ctx, md, NULL) != 1 || + EVP_DigestUpdate(md_ctx, message, message_len) != 1 || + EVP_DigestFinal_ex(md_ctx, digest, &digest_len) != 1) { + fprintf(stderr, "Failed to create message digest\n"); + print_openssl_errors(); + EVP_MD_CTX_free(md_ctx); + goto cleanup; + } + EVP_MD_CTX_free(md_ctx); + + if (sign_using_evp_pkey_sign(digest, digest_len, &signature3, &sig_len3, key, md)) { + printf(" Signature created successfully (length: %zu bytes)\n", sig_len3); + + /* Verify the signature */ + if (verify_using_evp_pkey_verify(digest, digest_len, signature3, sig_len3, key, md)) { + printf(" Signature verified successfully\n"); + } else { + printf(" Signature verification failed\n"); + goto cleanup; + } + } else { + printf(" Failed to create signature\n"); + goto cleanup; + } + + /* Test Pair 4: EVP_DigestSign with explicit PKEY_CTX */ + printf("\n4. Testing EVP_DigestSign with explicit PKEY_CTX (fine-grained control):\n"); + if (sign_using_digestsign_with_ctx(message, message_len, &signature4, &sig_len4, + key, md, param_setter)) { + printf(" Signature created successfully (length: %zu bytes)\n", sig_len4); + + /* Verify the signature */ + if (verify_using_digestverify_with_ctx(message, message_len, signature4, sig_len4, + key, md, param_setter)) { + printf(" Signature verified successfully\n"); + } else { + printf(" Signature verification failed\n"); + goto cleanup; + } + } else { + printf(" Failed to create signature\n"); + goto cleanup; + } + + result = 1; + +cleanup: + /* Free allocated resources */ + OPENSSL_free(signature1); + OPENSSL_free(signature2); + OPENSSL_free(signature3); + OPENSSL_free(signature4); + + return result; +} + +/* RSA-specific test function that uses both the generic API tests and the RSA-specific pair 5 */ +int test_signature_apis_rsa(void) { + EVP_PKEY *key = NULL; + EVP_PKEY_CTX *key_ctx = NULL; + const EVP_MD *md = NULL; + unsigned char *signature5 = NULL; + size_t sig_len5 = 0; + int result = 0; + RSA *rsa_key = NULL; + + /* Message to sign */ + const unsigned char message[] = "Test message for OpenSSL signature APIs"; + const size_t message_len = strlen((char *)message); + + /* Create a message digest */ + md = EVP_sha256(); + if (!md) { + fprintf(stderr, "Failed to get SHA256 digest\n"); + goto cleanup; + } + + /* Create an RSA key for testing */ + printf("Generating RSA key pair for testing...\n"); + key_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (!key_ctx) { + fprintf(stderr, "Failed to create key generation context\n"); + print_openssl_errors(); + goto cleanup; + } + + if (EVP_PKEY_keygen_init(key_ctx) <= 0) { + fprintf(stderr, "Failed to initialize key generation\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Use smaller key size for quicker test */ + if (EVP_PKEY_CTX_set_rsa_keygen_bits(key_ctx, 2048) <= 0) { + fprintf(stderr, "Failed to set key size\n"); + print_openssl_errors(); + goto cleanup; + } + + if (EVP_PKEY_keygen(key_ctx, &key) <= 0) { + fprintf(stderr, "Failed to generate key\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Extract RSA key for low-level functions */ + rsa_key = get_rsa_from_pkey(key); + if (!rsa_key) { + goto cleanup; + } + + /* Run the generic tests for APIs 1-4 */ + if (!test_signature_apis(key, md, set_rsa_pss_padding, "RSA")) { + fprintf(stderr, "Generic signature API tests failed\n"); + goto cleanup; + } + + /* Test Pair 5: Low-level RSA API (RSA-specific) */ + printf("\n5. Testing low-level RSA API (deprecated but still found in legacy code):\n"); + if (sign_using_rsa_sign(message, message_len, &signature5, &sig_len5, + rsa_key, NID_sha256, md)) { + printf(" Signature created successfully (length: %zu bytes)\n", sig_len5); + + /* Verify the signature */ + if (verify_using_rsa_verify(message, message_len, signature5, sig_len5, + rsa_key, NID_sha256, md)) { + printf(" Signature verified successfully\n"); + } else { + printf(" Signature verification failed\n"); + goto cleanup; + } + } else { + printf(" Failed to create signature\n"); + goto cleanup; + } + + printf("\nSummary of OpenSSL signature APIs with RSA:\n"); + printf("1. EVP_Sign API: Older, simpler API with fewer options\n"); + printf("2. EVP_DigestSign API: Modern recommended API with better algorithm support\n"); + printf("3. EVP_PKEY_sign API: Lower-level API requiring pre-hashed message\n"); + printf("4. EVP_DigestSign with explicit PKEY_CTX: For fine-grained parameter control\n"); + printf("5. Low-level RSA API: Deprecated but may be found in legacy code\n"); + + result = 1; + +cleanup: + /* Free allocated resources */ + OPENSSL_free(signature5); + RSA_free(rsa_key); /* RSA_free decrements reference count */ + EVP_PKEY_free(key); + EVP_PKEY_CTX_free(key_ctx); + + if (!result) { + fprintf(stderr, "Test failed with errors\n"); + print_openssl_errors(); + } + + return result; +} + +/* No special parameter setter needed for default DSA behavior */ +int no_parameter_setter(EVP_PKEY_CTX *ctx) { + /* No changes needed for default DSA parameters */ + return 1; +} + +/* Helper function to get DSA key from EVP_PKEY for low-level operations */ +DSA *get_dsa_from_pkey(EVP_PKEY *pkey) { + DSA *dsa = EVP_PKEY_get1_DSA(pkey); + if (!dsa) { + fprintf(stderr, "Failed to get DSA key from EVP_PKEY\n"); + print_openssl_errors(); + } + return dsa; +} + +/* Sign a message using low-level DSA functions */ +int sign_using_dsa_sign(const unsigned char *message, size_t message_len, + unsigned char **signature, size_t *signature_len, + DSA *dsa_key, const EVP_MD *md) { + unsigned char digest[EVP_MAX_MD_SIZE]; + unsigned int digest_len; + int ret = 0; + EVP_MD_CTX *md_ctx = NULL; + DSA_SIG *sig = NULL; + const BIGNUM *r = NULL, *s = NULL; + unsigned int r_len, s_len, bn_len; + + /* Create digest context */ + if (!(md_ctx = EVP_MD_CTX_new())) { + fprintf(stderr, "Failed to create EVP_MD_CTX\n"); + print_openssl_errors(); + return 0; + } + + /* Calculate hash of the message */ + if (EVP_DigestInit_ex(md_ctx, md, NULL) != 1 || + EVP_DigestUpdate(md_ctx, message, message_len) != 1 || + EVP_DigestFinal_ex(md_ctx, digest, &digest_len) != 1) { + fprintf(stderr, "Failed to create message digest\n"); + print_openssl_errors(); + EVP_MD_CTX_free(md_ctx); + return 0; + } + EVP_MD_CTX_free(md_ctx); + + /* Sign digest with DSA_do_sign */ + sig = DSA_do_sign(digest, digest_len, dsa_key); + if (sig == NULL) { + fprintf(stderr, "Failed to create DSA signature\n"); + print_openssl_errors(); + return 0; + } + + /* Extract r and s components from signature */ + DSA_SIG_get0(sig, &r, &s); + if (r == NULL || s == NULL) { + fprintf(stderr, "Failed to extract r and s from DSA_SIG\n"); + DSA_SIG_free(sig); + return 0; + } + + /* Determine the size of r and s */ + bn_len = DSA_size(dsa_key) / 2; + r_len = BN_num_bytes(r); + s_len = BN_num_bytes(s); + + /* Allocate memory for DER encoded signature */ + *signature_len = DSA_size(dsa_key); + *signature = OPENSSL_malloc(*signature_len); + if (!(*signature)) { + fprintf(stderr, "Failed to allocate memory for signature\n"); + print_openssl_errors(); + DSA_SIG_free(sig); + return 0; + } + + /* Create the signature buffer with r and s values */ + memset(*signature, 0, *signature_len); + + /* Copy r to the first half of the signature */ + if (BN_bn2bin(r, *signature + (bn_len - r_len)) <= 0) { + fprintf(stderr, "Failed to convert r to binary\n"); + print_openssl_errors(); + OPENSSL_free(*signature); + *signature = NULL; + DSA_SIG_free(sig); + return 0; + } + + /* Copy s to the second half of the signature */ + if (BN_bn2bin(s, *signature + bn_len + (bn_len - s_len)) <= 0) { + fprintf(stderr, "Failed to convert s to binary\n"); + print_openssl_errors(); + OPENSSL_free(*signature); + *signature = NULL; + DSA_SIG_free(sig); + return 0; + } + + DSA_SIG_free(sig); + ret = 1; + + return ret; +} + +/* Verify a signature using low-level DSA functions */ +int verify_using_dsa_verify(const unsigned char *message, size_t message_len, + const unsigned char *signature, size_t signature_len, + DSA *dsa_key, const EVP_MD *md) { + unsigned char digest[EVP_MAX_MD_SIZE]; + unsigned int digest_len; + int ret = 0; + EVP_MD_CTX *md_ctx = NULL; + DSA_SIG *sig = NULL; + BIGNUM *r = NULL, *s = NULL; + unsigned int bn_len; + + /* Create digest context */ + if (!(md_ctx = EVP_MD_CTX_new())) { + fprintf(stderr, "Failed to create EVP_MD_CTX\n"); + print_openssl_errors(); + return 0; + } + + /* Calculate hash of the message */ + if (EVP_DigestInit_ex(md_ctx, md, NULL) != 1 || + EVP_DigestUpdate(md_ctx, message, message_len) != 1 || + EVP_DigestFinal_ex(md_ctx, digest, &digest_len) != 1) { + fprintf(stderr, "Failed to create message digest\n"); + print_openssl_errors(); + EVP_MD_CTX_free(md_ctx); + return 0; + } + EVP_MD_CTX_free(md_ctx); + + /* Create a new DSA_SIG structure */ + sig = DSA_SIG_new(); + if (sig == NULL) { + fprintf(stderr, "Failed to create DSA_SIG structure\n"); + print_openssl_errors(); + return 0; + } + + /* Set up r and s BIGNUMs */ + r = BN_new(); + s = BN_new(); + if (r == NULL || s == NULL) { + fprintf(stderr, "Failed to create BIGNUM for r or s\n"); + print_openssl_errors(); + DSA_SIG_free(sig); + BN_free(r); + BN_free(s); + return 0; + } + + /* Determine the size of each component */ + bn_len = DSA_size(dsa_key) / 2; + + /* Convert binary signature to r and s components */ + if (BN_bin2bn(signature, bn_len, r) == NULL || + BN_bin2bn(signature + bn_len, bn_len, s) == NULL) { + fprintf(stderr, "Failed to convert binary signature to r and s\n"); + print_openssl_errors(); + DSA_SIG_free(sig); + BN_free(r); + BN_free(s); + return 0; + } + + /* Set r and s in the DSA_SIG structure */ + if (DSA_SIG_set0(sig, r, s) != 1) { + fprintf(stderr, "Failed to set r and s in DSA_SIG\n"); + print_openssl_errors(); + DSA_SIG_free(sig); + BN_free(r); + BN_free(s); + return 0; + } + + /* r and s are now owned by sig, don't free them separately */ + + /* Verify the signature */ + ret = DSA_do_verify(digest, digest_len, sig, dsa_key); + if (ret != 1) { + fprintf(stderr, "DSA signature verification failed\n"); + print_openssl_errors(); + } + + DSA_SIG_free(sig); + return (ret == 1); +} + +/* Test function for DSA signatures */ +int test_signature_apis_dsa(void) { + EVP_PKEY *key = NULL; + EVP_PKEY_CTX *key_ctx = NULL; + EVP_PKEY_CTX *param_ctx = NULL; + const EVP_MD *md = NULL; + unsigned char *signature5 = NULL; + size_t sig_len5 = 0; + int result = 0; + DSA *dsa_key = NULL; + EVP_PKEY *params = NULL; + + /* Message to sign */ + const unsigned char message[] = "Test message for OpenSSL signature APIs"; + const size_t message_len = strlen((char *)message); + + /* Create a message digest */ + md = EVP_sha256(); + if (!md) { + fprintf(stderr, "Failed to get SHA256 digest\n"); + goto cleanup; + } + + /* Create a DSA key for testing */ + printf("Generating DSA key pair for testing...\n"); + + /* Step 1: Create parameter generation context using the "DSA" algorithm name */ + param_ctx = EVP_PKEY_CTX_new_from_name(NULL, "DSA", NULL); + if (!param_ctx) { + fprintf(stderr, "Failed to create DSA parameter generation context\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Step 2: Initialize the parameter generation context */ + if (EVP_PKEY_paramgen_init(param_ctx) <= 0) { + fprintf(stderr, "Failed to initialize parameter generation\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Step 3: Set parameter size */ + if (EVP_PKEY_CTX_set_dsa_paramgen_bits(param_ctx, 2048) <= 0) { + fprintf(stderr, "Failed to set DSA parameter size\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Step 4: Generate the parameters */ + printf("Generating DSA parameters (this may take a moment)...\n"); + if (EVP_PKEY_paramgen(param_ctx, ¶ms) <= 0) { + fprintf(stderr, "Failed to generate DSA parameters\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Step 5: Create key generation context using the parameters */ + key_ctx = EVP_PKEY_CTX_new(params, NULL); + if (!key_ctx) { + fprintf(stderr, "Failed to create key generation context\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Step 6: Initialize for key generation */ + if (EVP_PKEY_keygen_init(key_ctx) <= 0) { + fprintf(stderr, "Failed to initialize key generation\n"); + print_openssl_errors(); + goto cleanup; + } + + /* Step 7: Generate the key pair */ + printf("Generating DSA key pair using parameters...\n"); + if (EVP_PKEY_keygen(key_ctx, &key) <= 0) { + fprintf(stderr, "Failed to generate DSA key\n"); + print_openssl_errors(); + goto cleanup; + } + + printf("DSA key generation completed successfully\n"); + + /* Extract DSA key for low-level functions */ + dsa_key = get_dsa_from_pkey(key); + if (!dsa_key) { + goto cleanup; + } + + /* Run the generic tests for APIs 1-4 */ + printf("\nRunning generic signature tests (APIs 1-4) with DSA\n"); + if (!test_signature_apis(key, md, no_parameter_setter, "DSA")) { + fprintf(stderr, "Generic signature API tests failed with DSA\n"); + goto cleanup; + } + + /* Test Pair 5: Low-level DSA API */ + printf("\n5. Testing low-level DSA API (deprecated but still found in legacy code):\n"); + if (sign_using_dsa_sign(message, message_len, &signature5, &sig_len5, dsa_key, md)) { + printf(" Signature created successfully (length: %zu bytes)\n", sig_len5); + + /* Verify the signature */ + if (verify_using_dsa_verify(message, message_len, signature5, sig_len5, dsa_key, md)) { + printf(" Signature verified successfully\n"); + } else { + printf(" Signature verification failed\n"); + goto cleanup; + } + } else { + printf(" Failed to create signature\n"); + goto cleanup; + } + + printf("\nSummary of OpenSSL signature APIs with DSA:\n"); + printf("1. EVP_Sign API: Older, simpler API with fewer options\n"); + printf("2. EVP_DigestSign API: Modern recommended API with better algorithm support\n"); + printf("3. EVP_PKEY_sign API: Lower-level API requiring pre-hashed message\n"); + printf("4. EVP_DigestSign with explicit PKEY_CTX: For fine-grained parameter control\n"); + printf("5. Low-level DSA API: Deprecated but may be found in legacy code\n"); + + result = 1; + +cleanup: + /* Free allocated resources */ + OPENSSL_free(signature5); + /* dsa_key is owned by the EVP_PKEY, don't free it separately */ + EVP_PKEY_free(params); + EVP_PKEY_free(key); + EVP_PKEY_CTX_free(param_ctx); + EVP_PKEY_CTX_free(key_ctx); + + if (!result) { + fprintf(stderr, "DSA signature test failed with errors\n"); + print_openssl_errors(); + } + + return result; +} + +/* Main function to run the test suite */ +int main(void) { + /* Initialize OpenSSL */ + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); + + int result = 0; + + printf("\n-------- TESTING RSA SIGNATURES --------\n"); + result = test_signature_apis_rsa(); + + printf("\n-------- TESTING DSA SIGNATURES --------\n"); + result &= test_signature_apis_dsa(); + + if (result) { + printf("\nAll tests completed successfully.\n"); + return 0; + } else { + fprintf(stderr, "\nSome tests failed.\n"); + return 1; + } +}