DigitalSignature.java

/*
 ** Module   : DigitalSignature.java
 ** Abstract : A tool to sign messages and verify their signature for certain private / public key pair.
 **
 ** Copyright (c) 2024, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- ---------------------------------Description---------------------------------
 ** 001 GBB 20240213 Created initial version
 */
/*
 ** 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 may find a copy of the GNU Affero GPL version 3 at the following
 ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
 **
 ** Additional terms under GNU Affero GPL version 3 section 7:
 **
 **   Under Section 7 of the GNU Affero GPL version 3, the following additional
 **   terms apply to the works covered under the License.  These additional terms
 **   are non-permissive additional terms allowed under Section 7 of the GNU
 **   Affero GPL version 3 and may not be removed by you.
 **
 **   0. Attribution Requirement.
 **
 **     You must preserve all legal notices or author attributions in the covered
 **     work or Appropriate Legal Notices displayed by works containing the covered
 **     work.  You may not remove from the covered work any author or developer
 **     credit already included within the covered work.
 **
 **   1. No License To Use Trademarks.
 **
 **     This license does not grant any license or rights to use the trademarks
 **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
 **     of Golden Code Development Corporation. You are not authorized to use the
 **     name Golden Code, FWD, or the names of any author or contributor, for
 **     publicity purposes without written authorization.
 **
 **   2. No Misrepresentation of Affiliation.
 **
 **     You may not represent yourself as Golden Code Development Corporation or FWD.
 **
 **     You may not represent yourself for publicity purposes as associated with
 **     Golden Code Development Corporation, FWD, or any author or contributor to
 **     the covered work, without written authorization.
 **
 **   3. No Misrepresentation of Source or Origin.
 **
 **     You may not represent the covered work as solely your work.  All modified
 **     versions of the covered work must be marked in a reasonable way to make it
 **     clear that the modified work is not originating from Golden Code Development
 **     Corporation or FWD.  All modified versions must contain the notices of
 **     attribution required in this license.
 */

package com.goldencode.p2j.security;

import com.goldencode.p2j.main.*;
import com.goldencode.p2j.util.logging.*;

import java.io.*;
import java.math.*;
import java.security.*;
import java.security.cert.*;
import java.security.cert.Certificate;

/** A tool to sign messages and verify their signature for certain private : public key pair. */
public class DigitalSignature
{
   /** The algorithm used for signing the values, corresponds to the server certificate algo. */
   private static final String ALGORITHM = "SHA256withRSA";
   
   /** Private key */
   private final PrivateKey privateKey;

   /** Public key */
   private final PublicKey publicKey;

   /**
    * Public constructor accepting private : public key pair.
    *
    * @param    privateKey
    *           Private key.
    * @param    publicKey
    *           Public key.
    */
   public DigitalSignature(PrivateKey privateKey, PublicKey publicKey)
   {
      this.privateKey = privateKey;
      this.publicKey = publicKey;
   }

   /**
    * Reads the server key store, retrieves the public and private key pair and returns an instance of 
    * {@link DigitalSignature}.
    * 
    * @return   An instance of {@link DigitalSignature} with the public and private key pair for the server.
    * 
    * @throws   KeyStoreException
    * @throws   UnrecoverableKeyException
    * @throws   NoSuchAlgorithmException
    * @throws   IOException
    * @throws   CertificateException
    */
   public static DigitalSignature fromServerKeyStore()
   throws KeyStoreException,
          UnrecoverableKeyException,
          NoSuchAlgorithmException,
          IOException,
          CertificateException
   {
      ServerKeyStore serverKeyStore = ServerKeyStore.getStore();
      byte[] keyStoreContent = serverKeyStore.getKeyStore();
      char[] keyStorePassword = serverKeyStore.getKeyStorePassword().toCharArray();
      char[] managerPassword = serverKeyStore.getKeyManagerPassword().toCharArray();
      String alias = serverKeyStore.getTrustServerAlias();

      KeyStore ks = KeyStore.getInstance("JKS");
      ks.load(new ByteArrayInputStream(keyStoreContent), keyStorePassword);

      PrivateKey privateKey = (PrivateKey) ks.getKey(alias, managerPassword);
      Certificate certificate = ks.getCertificate(alias);
      PublicKey publicKey = certificate.getPublicKey();

      return new DigitalSignature(privateKey, publicKey);
   }

   /**
    * Converts a byte array into a hex format.
    *
    * @param   bytes
    *          The byte array to be converted.
    *
    * @return  The converted string.
    */
   public static String toHex(byte[] bytes)
   {
      StringBuilder sb = new StringBuilder();
      for (byte b : bytes) {
         sb.append(String.format("%02X", b));
      }
      return sb.toString();
   }

   /**
    * Converts a hex formatted string back to a byte array.
    *
    * @param   hexText
    *          Hex formatted string.
    *
    * @return  Byte array.
    */
   public static byte[] fromHex(String hexText)
   {
      byte[] bytes = new BigInteger(hexText, 16).toByteArray();
      if (bytes[0] == 0) {
         // cleans up the leading zero
         byte[] newBytes = new byte[bytes.length - 1];
         System.arraycopy(bytes, 1, newBytes, 0, newBytes.length);
         bytes = newBytes;
      }
      return bytes;
   }

   /**
    * Signs the message with the private key and returns the signed value.
    * 
    * @param    message
    *           The message to sign.
    *
    * @return   The signed message.
    *
    * @throws   SignatureException
    * @throws   InvalidKeyException
    * @throws   NoSuchAlgorithmException
    */
   public byte[] sign(String message)
   throws SignatureException,
          InvalidKeyException,
          NoSuchAlgorithmException
   {
      Signature signature = Signature.getInstance(ALGORITHM);
      signature.initSign(privateKey);
      signature.update(message.getBytes());
      
      return signature.sign();
   }

   /**
    * Signs the message with the private key and returns the signed value.
    *
    * @param    originalMessage
    *           The original message.
    * @param    signedMessage
    *           The signed message.
    *
    * @return   <code>true</code> if the signedMessage is generated by the key, <code>false</code> otherwise.
    *
    * @throws   SignatureException
    * @throws   InvalidKeyException
    * @throws   NoSuchAlgorithmException
    */
   public boolean verify(String originalMessage, byte[] signedMessage)
   throws SignatureException,
          InvalidKeyException,
          NoSuchAlgorithmException
   {
      Signature signature = Signature.getInstance(ALGORITHM);
      signature.initVerify(publicKey);
      signature.update(originalMessage.getBytes());
      
      return signature.verify(signedMessage);
   }

   /**
    * Test for signing messages.
    *
    * @param    args
    *           Unused.
    */
   public static void main(String[] args)
   throws Exception
   {
      DigitalSignature deviceIdSignature = fromServerKeyStore();
      
      String originalMessage = "test";
      byte[] signedMessage = deviceIdSignature.sign(originalMessage);

      String hex = toHex(signedMessage);
      byte[] signedMessage1 = fromHex(hex);

      boolean isValid = deviceIdSignature.verify(originalMessage, signedMessage1);
      CentralLogger.get(DigitalSignature.class).warning("isValid: " + isValid);
   }
}