view Sources/Security/RSAPublicKey.cpp @ 52:f65e4503cd67

added test_dicom_permissions
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 22 Oct 2025 12:05:32 +0200
parents 86c6ac51044a
children 0f8c46d755e2
line wrap: on
line source

/**
 * SPDX-FileCopyrightText: 2024-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

/**
 * Orthanc for Education
 * Copyright (C) 2024-2025 Sebastien Jodogne, EPL UCLouvain, Belgium
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program 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
 * Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 **/


#include "RSAPublicKey.h"

#include "../HttpToolbox.h"
#include "OpenSSLSerializationContext.h"
#include "RSAPrivateKey.h"
#include "SecurityConstants.h"

#include <SerializationToolbox.h>

#include <openssl/core_names.h>
#include <openssl/param_build.h>
#include <openssl/pem.h>


void RSAPublicKey::LoadFromPrivate(RSAPrivateKey& privateKey)
{
  std::string pem;
  privateKey.SerializePublic(pem);
  Unserialize(pem);
}


void RSAPublicKey::Serialize(std::string& pem)
{
  if (!IsValid())
  {
    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
  }

  OpenSSLSerializationContext context;

  if (!PEM_write_bio_PUBKEY(context.GetValue(), key_.GetValue()))
  {
    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Failed to write public key to BIO");
  }

  context.Write(pem);
}


void RSAPublicKey::Unserialize(const std::string& pem)
{
  PointerRAII<BIO> bio(BIO_free, 1 /* success code of BIO_free() */);
  bio.Assign(BIO_new_mem_buf(pem.empty() ? NULL : pem.c_str(), pem.size()));

  key_.Assign(PEM_read_bio_PUBKEY(bio.GetValue(), NULL, NULL, NULL));

  if (EVP_PKEY_base_id(key_.GetValue()) != EVP_PKEY_RSA)
  {
    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "The PEM does not contain a RSA private key");
  }

  PointerRAII<EVP_PKEY_CTX> context(EVP_PKEY_CTX_free);
  context.Assign(EVP_PKEY_CTX_new(key_.GetValue(), NULL));

  if (EVP_PKEY_private_check(context.GetValue()) != 0)
  {
    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "The PEM contains a RSA private key, not a public key");
  }
}


bool RSAPublicKey::VerifyRS256(const std::string& signature,
                               const std::string& message)
{
  // $ openssl dgst -sha256 -verify /tmp/key -keyform PEM -signature /tmp/signature /tmp/message

  if (!IsValid())
  {
    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
  }

  PointerRAII<EVP_MD_CTX> context(EVP_MD_CTX_free);
  context.Assign(EVP_MD_CTX_new());

  if (EVP_DigestVerifyInit(context.GetValue(), NULL, EVP_sha256(), NULL, key_.GetValue()) != 1 ||
      EVP_DigestVerifyUpdate(context.GetValue(), message.empty() ? NULL : message.c_str(), message.size()) != 1)
  {
    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Cannot verify a signature");
  }

  return (EVP_DigestVerifyFinal(context.GetValue(),
                                signature.empty() ? NULL : reinterpret_cast<const unsigned char*>(signature.c_str()),
                                signature.size()) == 1);
}


void RSAPublicKey::Import_JWKS_RS256(const Json::Value& key)
{
  if (Orthanc::SerializationToolbox::ReadString(key, JWKS_FIELD_ALG) != "RS256" ||
      Orthanc::SerializationToolbox::ReadString(key, JWKS_FIELD_KTY) != "RSA" ||
      Orthanc::SerializationToolbox::ReadString(key, JWKS_FIELD_USE) != "sig")
  {
    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
  }

  std::string encodedN = Orthanc::SerializationToolbox::ReadString(key, JWKS_FIELD_N);
  std::string encodedE = Orthanc::SerializationToolbox::ReadString(key, JWKS_FIELD_E);

  std::string n, e;
  HttpToolbox::DecodeBase64Url(n, encodedN);
  HttpToolbox::DecodeBase64Url(e, encodedE);

  PointerRAII<BIGNUM> bignumN(BN_free);
  bignumN.Assign(BN_bin2bn(reinterpret_cast<const unsigned char*>(n.c_str()), n.size(), NULL /* create new object */));

  PointerRAII<BIGNUM> bignumE(BN_free);
  bignumE.Assign(BN_bin2bn(reinterpret_cast<const unsigned char*>(e.c_str()), e.size(), NULL /* create new object */));

  PointerRAII<OSSL_PARAM_BLD> builder(OSSL_PARAM_BLD_free);
  builder.Assign(OSSL_PARAM_BLD_new());

  if (OSSL_PARAM_BLD_push_BN(builder.GetValue(), OSSL_PKEY_PARAM_RSA_N, bignumN.GetValue()) != 1 ||
      OSSL_PARAM_BLD_push_BN(builder.GetValue(), OSSL_PKEY_PARAM_RSA_E, bignumE.GetValue()) != 1)
  {
    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
  }

  PointerRAII<OSSL_PARAM> params(OSSL_PARAM_free);
  params.Assign(OSSL_PARAM_BLD_to_param(builder.GetValue()));

  PointerRAII<EVP_PKEY_CTX> context(EVP_PKEY_CTX_free);
  context.Assign(EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL));

  key_.Clear();

  if (EVP_PKEY_fromdata_init(context.GetValue()) != 1 ||
      EVP_PKEY_fromdata(context.GetValue(), &key_.GetValue(), EVP_PKEY_PUBLIC_KEY, params.GetValue()) != 1)
  {
    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
  }
}