AlgorithmRegistry.java

/*
 * Copyright 2025 The Sigstore Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package dev.sigstore;

import java.math.BigInteger;
import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Map;

public class AlgorithmRegistry {

  // order to differentiate between the various ec curves
  private static final Map<BigInteger, SigningAlgorithm> ECDSA_ORDERS =
      Map.of(
          new BigInteger(
                  "115792089210356248762697446949407573529996955224135760342422259061068512044369"),
              SigningAlgorithm.PKIX_ECDSA_P256_SHA_256,
          new BigInteger(
                  "39402006196394479212279040100143613805079739270465446667946905279627659399113263569398956308152294913554433653942643"),
              SigningAlgorithm.PKIX_ECDSA_P384_SHA_384,
          new BigInteger(
                  "6864797660130609714981900799081393217269435300143305409394463459185543183397655394245057746333217197532963996371363321113864768612440380340372808892707005449"),
              SigningAlgorithm.PKIX_ECDSA_P521_SHA_512);

  /**
   * Determine the signing algorithm based on the public key.
   *
   * @param publicKey the public key
   * @return the signing algorithm
   * @throws UnsupportedAlgorithmException if the key algorithm or curve is not supported
   * @throws IllegalStateException if the public key cannot be converted into a known type
   */
  public static SigningAlgorithm getSigningAlgorithm(PublicKey publicKey)
      throws UnsupportedAlgorithmException {
    String algorithm = publicKey.getAlgorithm();
    if ("RSA".equals(algorithm)) {
      if (publicKey instanceof RSAPublicKey) {
        var rsaKey = (RSAPublicKey) publicKey;
        int bitLength = rsaKey.getModulus().bitLength();
        if (bitLength == 2048) {
          return SigningAlgorithm.PKIX_RSA_PKCS1V15_2048_SHA256;
        } else if (bitLength == 3072) {
          return SigningAlgorithm.PKIX_RSA_PKCS1V15_3072_SHA256;
        } else if (bitLength == 4096) {
          return SigningAlgorithm.PKIX_RSA_PKCS1V15_4096_SHA256;
        }
        throw new UnsupportedAlgorithmException("Unsupported RSA bit length: " + bitLength);
      }
      throw new IllegalStateException("RSA key must be an instance of RSAPublicKey");
    }
    if ("EC".equals(algorithm) || "ECDSA".equals(algorithm)) {
      if (publicKey instanceof ECPublicKey) {
        var ecKey = (ECPublicKey) publicKey;
        var order = ecKey.getParams().getOrder();
        var signingAlgorithm = ECDSA_ORDERS.get(order);
        if (signingAlgorithm == null) {
          throw new UnsupportedAlgorithmException("Unsupported EC key with order: " + order);
        }
        return signingAlgorithm;
      }
      throw new IllegalStateException("EC/ECDSA key must be an instance of ECPublicKey");
    }
    throw new UnsupportedAlgorithmException("Unsupported key algorithm: " + algorithm);
  }

  public enum SigningAlgorithm {
    PKIX_RSA_PKCS1V15_2048_SHA256(HashAlgorithm.SHA2_256),
    PKIX_RSA_PKCS1V15_3072_SHA256(HashAlgorithm.SHA2_256),
    PKIX_RSA_PKCS1V15_4096_SHA256(HashAlgorithm.SHA2_256),

    // ECDSA
    PKIX_ECDSA_P256_SHA_256(HashAlgorithm.SHA2_256),
    PKIX_ECDSA_P384_SHA_384(HashAlgorithm.SHA2_384),
    PKIX_ECDSA_P521_SHA_512(HashAlgorithm.SHA2_512);

    private final HashAlgorithm hashAlgorithm;

    SigningAlgorithm(HashAlgorithm hashAlgorithm) {
      this.hashAlgorithm = hashAlgorithm;
    }

    public HashAlgorithm getHashAlgorithm() {
      return hashAlgorithm;
    }
  }

  public enum HashAlgorithm {
    SHA2_256("SHA256", "sha256", 32),
    SHA2_384("SHA384", "sha384", 48),
    SHA2_512("SHA512", "sha512", 64);

    private final String name;
    private final String lowercase;
    private final int length;

    HashAlgorithm(String name, String lowercase, int length) {
      this.name = name;
      this.lowercase = lowercase;
      this.length = length;
    }

    public String toString() {
      return name;
    }

    public String toLowercaseString() {
      return lowercase;
    }

    public int getLength() {
      return length;
    }
  }
}