001/*
002 * Copyright 2015-2024 the original author or authors
003 *
004 * This software is licensed under the Apache License, Version 2.0,
005 * the GNU Lesser General Public License version 2 or later ("LGPL")
006 * and the WTFPL.
007 * You may choose either license to govern your use of this software only
008 * upon the condition that you accept all of the terms of either
009 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
010 */
011package org.minidns.dnssec.algorithms;
012
013import org.minidns.dnssec.DnssecValidationFailedException;
014import org.minidns.dnssec.DnssecValidationFailedException.DataMalformedException;
015import org.minidns.dnssec.DnssecValidationFailedException.DnssecInvalidKeySpecException;
016import org.minidns.dnssec.SignatureVerifier;
017import org.minidns.record.DNSKEY;
018import org.minidns.record.RRSIG;
019
020import java.io.ByteArrayOutputStream;
021import java.math.BigInteger;
022import java.security.InvalidKeyException;
023import java.security.KeyFactory;
024import java.security.NoSuchAlgorithmException;
025import java.security.PublicKey;
026import java.security.Signature;
027import java.security.SignatureException;
028
029public abstract class JavaSecSignatureVerifier implements SignatureVerifier {
030    private final KeyFactory keyFactory;
031    private final String signatureAlgorithm;
032
033    public JavaSecSignatureVerifier(String keyAlgorithm, String signatureAlgorithm) throws NoSuchAlgorithmException {
034        keyFactory = KeyFactory.getInstance(keyAlgorithm);
035        this.signatureAlgorithm = signatureAlgorithm;
036
037        // Verify signature algorithm to be valid
038        Signature.getInstance(signatureAlgorithm);
039    }
040
041    public KeyFactory getKeyFactory() {
042        return keyFactory;
043    }
044
045    @Override
046    public boolean verify(byte[] content, RRSIG rrsig, DNSKEY key) throws DnssecValidationFailedException {
047        try {
048            PublicKey publicKey = getPublicKey(key);
049            Signature signature = Signature.getInstance(signatureAlgorithm);
050            signature.initVerify(publicKey);
051            signature.update(content);
052            return signature.verify(getSignature(rrsig));
053        } catch (NoSuchAlgorithmException e) {
054            // We checked against this before, it should never happen!
055            throw new AssertionError(e);
056        } catch (InvalidKeyException | SignatureException | ArithmeticException e) {
057            throw new DnssecValidationFailedException("Validating signature failed", e);
058        }
059    }
060
061    /**
062     * Encode the two unsigned integers r and s of a DSA or ECDSA signature as ASN.1 DER
063     * {@code SEQUENCE { INTEGER r, INTEGER s }}, as expected by {@link Signature#verify(byte[])}.
064     * <p>
065     * DER requires the minimal two's complement encoding of each INTEGER: leading zero bytes have to be removed,
066     * unless they are required because the following byte has its high bit set. Strict DER parsers, like the one of
067     * the SunEC provider, reject non-minimal encodings.
068     * </p>
069     *
070     * @param r the unsigned big-endian r value.
071     * @param s the unsigned big-endian s value.
072     * @return the DER encoded signature.
073     */
074    static byte[] toDerSignature(byte[] r, byte[] s) {
075        byte[] rDer = new BigInteger(1, r).toByteArray();
076        byte[] sDer = new BigInteger(1, s).toByteArray();
077
078        int sequenceLength = 2 + rDer.length + 2 + sDer.length;
079        // Only the DER short form length encoding is implemented, which is sufficient for DSA (20 byte r and s) and
080        // ECDSA P-256 (32 byte r and s) and P-384 (48 byte r and s).
081        if (sequenceLength > 127) {
082            throw new IllegalArgumentException("The DER encoding of r (" + r.length + " bytes) and s (" + s.length
083                    + " bytes) requires a SEQUENCE length of " + sequenceLength
084                    + " bytes, but only up to 127 bytes (DER short form length) are supported");
085        }
086
087        ByteArrayOutputStream bos = new ByteArrayOutputStream(2 + sequenceLength);
088        bos.write(0x30);
089        bos.write(sequenceLength);
090        bos.write(0x02);
091        bos.write(rDer.length);
092        bos.write(rDer, 0, rDer.length);
093        bos.write(0x02);
094        bos.write(sDer.length);
095        bos.write(sDer, 0, sDer.length);
096        return bos.toByteArray();
097    }
098
099    protected abstract byte[] getSignature(RRSIG rrsig) throws DataMalformedException;
100
101    protected abstract PublicKey getPublicKey(DNSKEY key) throws DataMalformedException, DnssecInvalidKeySpecException;
102}