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 org.minidns.constants.DnssecConstants.SignatureAlgorithm;
014import org.minidns.dnsname.DnsName;
015import org.minidns.record.Record.TYPE;
016import org.minidns.util.Base64;
017
018import java.io.ByteArrayInputStream;
019import java.io.DataInputStream;
020import java.io.DataOutputStream;
021import java.io.IOException;
022import java.text.SimpleDateFormat;
023import java.util.Date;
024import java.util.TimeZone;
025
026/**
027 * RRSIG record payload.
028 */
029public class RRSIG extends Data {
030
031    /**
032     * The type of RRset covered by this signature.
033     */
034    public final TYPE typeCovered;
035
036    /**
037     * The binary value of the type of RRset covered by this signature. In contrast to {@link #typeCovered}, this also
038     * holds the actual value if the type is unknown to MiniDNS.
039     */
040    public final int typeCoveredValue;
041
042    /**
043     * The cryptographic algorithm used to create the signature.
044     */
045    public final SignatureAlgorithm algorithm;
046
047    /**
048     * The cryptographic algorithm used to create the signature.
049     */
050    public final byte algorithmByte;
051
052    /**
053     * The number of labels in the original RRSIG RR owner name.
054     */
055    public final byte labels;
056
057    /**
058     * The TTL of the covered RRset.
059     */
060    public final long /* unsigned int */ originalTtl;
061
062    /**
063     * The date and time this RRSIG records expires.
064     */
065    public final Date signatureExpiration;
066
067    /**
068     * The date and time this RRSIG records starts to be valid.
069     */
070    public final Date signatureInception;
071
072    /**
073     * The key tag value of the DNSKEY RR that validates this signature.
074     */
075    public final int /* unsigned short */  keyTag;
076
077    /**
078     * The owner name of the DNSKEY RR that a validator is supposed to use.
079     */
080    public final DnsName signerName;
081
082    /**
083     * Signature that covers RRSIG RDATA (excluding the signature field) and RRset data.
084     */
085    private final byte[] signature;
086
087    @SuppressWarnings("JavaUtilDate")
088    public static RRSIG parse(DataInputStream dis, byte[] data, int length) throws IOException {
089        int typeCoveredValue = dis.readUnsignedShort();
090        TYPE typeCovered = TYPE.getType(typeCoveredValue);
091        byte algorithm = dis.readByte();
092        byte labels = dis.readByte();
093        long originalTtl = dis.readInt() & 0xFFFFFFFFL;
094        Date signatureExpiration = new Date((dis.readInt() & 0xFFFFFFFFL) * 1000);
095        Date signatureInception = new Date((dis.readInt() & 0xFFFFFFFFL) * 1000);
096        int keyTag = dis.readUnsignedShort();
097        DnsName signerName = DnsName.parse(dis, data);
098        int sigSize = length - signerName.size() - 18;
099        byte[] signature = new byte[sigSize];
100        if (dis.read(signature) != signature.length) throw new IOException();
101        return new RRSIG(typeCovered, typeCoveredValue, null, algorithm, labels, originalTtl, signatureExpiration, signatureInception, keyTag,
102                signerName, signature);
103    }
104
105    private  RRSIG(TYPE typeCovered, int typeCoveredValue, SignatureAlgorithm algorithm, byte algorithmByte, byte labels, long originalTtl,
106            Date signatureExpiration, Date signatureInception, int keyTag, DnsName signerName, byte[] signature) {
107        this.typeCovered = typeCovered;
108        this.typeCoveredValue = typeCoveredValue;
109
110        assert algorithmByte == (algorithm != null ? algorithm.number : algorithmByte);
111        this.algorithmByte = algorithmByte;
112        this.algorithm = algorithm != null ? algorithm : SignatureAlgorithm.forByte(algorithmByte);
113
114        this.labels = labels;
115        this.originalTtl = originalTtl;
116        this.signatureExpiration = signatureExpiration;
117        this.signatureInception = signatureInception;
118        this.keyTag = keyTag;
119        this.signerName = signerName;
120        this.signature = signature;
121    }
122
123    public RRSIG(TYPE typeCovered, int algorithm, byte labels, long originalTtl, Date signatureExpiration, 
124            Date signatureInception, int keyTag, DnsName signerName, byte[] signature) {
125            this(typeCovered, typeValueOf(typeCovered), null, (byte) algorithm, labels, originalTtl, signatureExpiration, signatureInception, keyTag,
126                    signerName, signature);
127    }
128
129    public RRSIG(TYPE typeCovered, int algorithm, byte labels, long originalTtl, Date signatureExpiration, 
130            Date signatureInception, int keyTag, String signerName, byte[] signature) {
131            this(typeCovered, typeValueOf(typeCovered), null, (byte) algorithm, labels, originalTtl, signatureExpiration, signatureInception, keyTag,
132                    DnsName.from(signerName), signature);
133    }
134
135    public RRSIG(TYPE typeCovered, SignatureAlgorithm algorithm, byte labels,
136            long originalTtl, Date signatureExpiration, Date signatureInception,
137            int keyTag, DnsName signerName, byte[] signature) {
138        this(typeCovered, algorithm.number, labels, originalTtl, signatureExpiration, signatureInception,
139                keyTag, signerName, signature);
140    }
141
142    public RRSIG(TYPE typeCovered, SignatureAlgorithm algorithm, byte labels,
143            long originalTtl, Date signatureExpiration, Date signatureInception,
144            int keyTag, String signerName, byte[] signature) {
145        this(typeCovered, algorithm.number, labels, originalTtl, signatureExpiration, signatureInception,
146                keyTag, DnsName.from(signerName), signature);
147    }
148
149    private static int typeValueOf(TYPE typeCovered) {
150        // Some users construct RRSIGs without a covered type, e.g. if they are only interested in the signature.
151        if (typeCovered == null) {
152            return TYPE.UNKNOWN.getValue();
153        }
154        return typeCovered.getValue();
155    }
156
157    public byte[] getSignature() {
158        return signature.clone();
159    }
160
161    public DataInputStream getSignatureAsDataInputStream() {
162        return new DataInputStream(new ByteArrayInputStream(signature));
163    }
164
165    public int getSignatureLength() {
166        return signature.length;
167    }
168
169    private transient String base64SignatureCache;
170
171    public String getSignatureBase64() {
172        if (base64SignatureCache == null) {
173            base64SignatureCache = Base64.encodeToString(signature);
174        }
175        return base64SignatureCache;
176    }
177
178    @Override
179    public TYPE getType() {
180        return TYPE.RRSIG;
181    }
182
183    @Override
184    public void serialize(DataOutputStream dos) throws IOException {
185        writePartialSignature(dos);
186        dos.write(signature);
187    }
188
189    @SuppressWarnings("JavaUtilDate")
190    public void writePartialSignature(DataOutputStream dos) throws IOException {
191        dos.writeShort(typeCoveredValue);
192        dos.writeByte(algorithmByte);
193        dos.writeByte(labels);
194        dos.writeInt((int) originalTtl);
195        dos.writeInt((int) (signatureExpiration.getTime() / 1000));
196        dos.writeInt((int) (signatureInception.getTime() / 1000));
197        dos.writeShort(keyTag);
198        signerName.writeToStream(dos);
199    }
200
201    @Override
202    public String toString() {
203        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
204        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
205        return TYPE.asString(typeCoveredValue) + " "
206                + algorithm + " "
207                + labels + " "
208                + originalTtl + " "
209                + dateFormat.format(signatureExpiration) + " "
210                + dateFormat.format(signatureInception) + " "
211                + keyTag + " "
212                + signerName + ". "
213                + getSignatureBase64();
214    }
215}