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.record;
012
013import java.io.DataInputStream;
014import java.io.DataOutputStream;
015import java.io.IOException;
016import java.math.BigInteger;
017import java.util.Arrays;
018import java.util.HashMap;
019import java.util.Map;
020
021public class TLSA extends Data {
022
023    private static final Map<Byte, CertUsage> CERT_USAGE_LUT = new HashMap<>();
024
025    /**
026     * The certificate usage field.
027     *
028     * @see <a href="https://tools.ietf.org/html/rfc6698#section-2.1.1">RFC 6698 ยง 2.1.1</a>
029     *
030     */
031    public enum CertUsage {
032
033        /**
034         * The given <b>CA</b> certificate (or its public key) MUST be found in at least
035         * one PKIX path to the end entity certificate.
036         *
037         * <p>
038         * PKIX-TA(0)
039         * </p>
040         */
041        caConstraint((byte) 0),
042
043        /**
044         * The given certificate (or its public key) MUST match the end entity
045         * certificate and MUST pass PKIX validation. Note that the requirement to pass
046         * PKIX validation is what makes this different from
047         * {@link #domainIssuedCertificate}.
048         *
049         * <p>
050         * PKIX-EE(1)
051         * </p>
052         */
053        serviceCertificateConstraint((byte) 1),
054
055        /**
056         * The given certificate (or its public key) MUST be used as trust anchor when
057         * validating the end entity certificate.
058         *
059         * <p>
060         * DANE-TA(2)
061         * </p>
062         */
063        trustAnchorAssertion((byte) 2),
064
065        /**
066         * The given certificate (or its public key) MUST match the end entity
067         * certificate. Unlike {@link #serviceCertificateConstraint}, this does not
068         * require PKIX validation.
069         *
070         * <p>
071         * DANE-EE(3)
072         * </p>
073         */
074        domainIssuedCertificate((byte) 3),
075        ;
076
077        public final byte byteValue;
078
079        CertUsage(byte byteValue) {
080            this.byteValue = byteValue;
081            CERT_USAGE_LUT.put(byteValue, this);
082        }
083    }
084
085    private static final Map<Byte, Selector> SELECTOR_LUT = new HashMap<>();
086
087    public enum Selector {
088        fullCertificate((byte) 0),
089        subjectPublicKeyInfo((byte) 1),
090        ;
091
092        public final byte byteValue;
093
094         Selector(byte byteValue) {
095            this.byteValue = byteValue;
096            SELECTOR_LUT.put(byteValue, this);
097        }
098    }
099
100    private static final Map<Byte, MatchingType> MATCHING_TYPE_LUT = new HashMap<>();
101
102    public enum MatchingType {
103        noHash((byte) 0),
104        sha256((byte) 1),
105        sha512((byte) 2),
106        ;
107
108        public final byte byteValue;
109
110        MatchingType(byte byteValue) {
111            this.byteValue = byteValue;
112            MATCHING_TYPE_LUT.put(byteValue, this);
113        }
114    }
115
116    static {
117        // Ensure that the LUTs are initialized.
118        CertUsage.values();
119        Selector.values();
120        MatchingType.values();
121    }
122
123    /**
124     * The provided association that will be used to match the certificate presented in
125     * the TLS handshake.
126     */
127    public final byte certUsageByte;
128
129    public final CertUsage certUsage;
130
131    /**
132     * Which part of the TLS certificate presented by the server will be matched against the
133     * association data.
134     */
135    public final byte selectorByte;
136
137    public final Selector selector;
138
139    /**
140     * How the certificate association is presented.
141     */
142    public final byte matchingTypeByte;
143
144    public final MatchingType matchingType;
145
146    /**
147     * The "certificate association data" to be matched.
148     */
149    private final byte[] certificateAssociation;
150
151    public static TLSA parse(DataInputStream dis, int length) throws IOException {
152        byte certUsage = dis.readByte();
153        byte selector = dis.readByte();
154        byte matchingType = dis.readByte();
155        byte[] certificateAssociation = new byte[length - 3];
156        if (dis.read(certificateAssociation) != certificateAssociation.length) throw new IOException();
157        return new TLSA(certUsage, selector, matchingType, certificateAssociation);
158    }
159
160    TLSA(byte certUsageByte, byte selectorByte, byte matchingTypeByte, byte[] certificateAssociation) {
161        this.certUsageByte = certUsageByte;
162        this.certUsage = CERT_USAGE_LUT.get(certUsageByte);
163
164        this.selectorByte = selectorByte;
165        this.selector = SELECTOR_LUT.get(selectorByte);
166
167        this.matchingTypeByte = matchingTypeByte;
168        this.matchingType = MATCHING_TYPE_LUT.get(matchingTypeByte);
169
170        this.certificateAssociation = certificateAssociation;
171    }
172
173    @Override
174    public Record.TYPE getType() {
175        return Record.TYPE.TLSA;
176    }
177
178    @Override
179    public void serialize(DataOutputStream dos) throws IOException {
180        dos.writeByte(certUsageByte);
181        dos.writeByte(selectorByte);
182        dos.writeByte(matchingTypeByte);
183        dos.write(certificateAssociation);
184    }
185
186    @Override
187    public String toString() {
188        return new StringBuilder()
189                .append(certUsageByte).append(' ')
190                .append(selectorByte).append(' ')
191                .append(matchingTypeByte).append(' ')
192                .append(new BigInteger(1, certificateAssociation).toString(16)).toString();
193    }
194
195    public byte[] getCertificateAssociation() {
196        return certificateAssociation.clone();
197    }
198
199    public boolean certificateAssociationEquals(byte[] otherCertificateAssociation) {
200        return Arrays.equals(certificateAssociation, otherCertificateAssociation);
201    }
202}