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 cryptographic algorithm used to create the signature.
038     */
039    public final SignatureAlgorithm algorithm;
040
041    /**
042     * The cryptographic algorithm used to create the signature.
043     */
044    public final byte algorithmByte;
045
046    /**
047     * The number of labels in the original RRSIG RR owner name.
048     */
049    public final byte labels;
050
051    /**
052     * The TTL of the covered RRset.
053     */
054    public final long /* unsigned int */ originalTtl;
055
056    /**
057     * The date and time this RRSIG records expires.
058     */
059    public final Date signatureExpiration;
060
061    /**
062     * The date and time this RRSIG records starts to be valid.
063     */
064    public final Date signatureInception;
065
066    /**
067     * The key tag value of the DNSKEY RR that validates this signature.
068     */
069    public final int /* unsigned short */  keyTag;
070
071    /**
072     * The owner name of the DNSKEY RR that a validator is supposed to use.
073     */
074    public final DnsName signerName;
075
076    /**
077     * Signature that covers RRSIG RDATA (excluding the signature field) and RRset data.
078     */
079    private final byte[] signature;
080
081    @SuppressWarnings("JavaUtilDate")
082    public static RRSIG parse(DataInputStream dis, byte[] data, int length) throws IOException {
083        TYPE typeCovered = TYPE.getType(dis.readUnsignedShort());
084        byte algorithm = dis.readByte();
085        byte labels = dis.readByte();
086        long originalTtl = dis.readInt() & 0xFFFFFFFFL;
087        Date signatureExpiration = new Date((dis.readInt() & 0xFFFFFFFFL) * 1000);
088        Date signatureInception = new Date((dis.readInt() & 0xFFFFFFFFL) * 1000);
089        int keyTag = dis.readUnsignedShort();
090        DnsName signerName = DnsName.parse(dis, data);
091        int sigSize = length - signerName.size() - 18;
092        byte[] signature = new byte[sigSize];
093        if (dis.read(signature) != signature.length) throw new IOException();
094        return new RRSIG(typeCovered, null, algorithm, labels, originalTtl, signatureExpiration, signatureInception, keyTag, signerName,
095                signature);
096    }
097
098    private  RRSIG(TYPE typeCovered, SignatureAlgorithm algorithm, byte algorithmByte, byte labels, long originalTtl, Date signatureExpiration, 
099            Date signatureInception, int keyTag, DnsName signerName, byte[] signature) {
100        this.typeCovered = typeCovered;
101
102        assert algorithmByte == (algorithm != null ? algorithm.number : algorithmByte);
103        this.algorithmByte = algorithmByte;
104        this.algorithm = algorithm != null ? algorithm : SignatureAlgorithm.forByte(algorithmByte);
105
106        this.labels = labels;
107        this.originalTtl = originalTtl;
108        this.signatureExpiration = signatureExpiration;
109        this.signatureInception = signatureInception;
110        this.keyTag = keyTag;
111        this.signerName = signerName;
112        this.signature = signature;
113    }
114
115    public RRSIG(TYPE typeCovered, int algorithm, byte labels, long originalTtl, Date signatureExpiration, 
116            Date signatureInception, int keyTag, DnsName signerName, byte[] signature) {
117            this(typeCovered, null, (byte) algorithm, labels, originalTtl, signatureExpiration, signatureInception, keyTag, signerName, signature);
118    }
119
120    public RRSIG(TYPE typeCovered, int algorithm, byte labels, long originalTtl, Date signatureExpiration, 
121            Date signatureInception, int keyTag, String signerName, byte[] signature) {
122            this(typeCovered, null, (byte) algorithm, labels, originalTtl, signatureExpiration, signatureInception, keyTag, DnsName.from(signerName), signature);
123    }
124
125    public RRSIG(TYPE typeCovered, SignatureAlgorithm algorithm, byte labels,
126            long originalTtl, Date signatureExpiration, Date signatureInception,
127            int keyTag, DnsName signerName, byte[] signature) {
128        this(typeCovered, algorithm.number, labels, originalTtl, signatureExpiration, signatureInception,
129                keyTag, signerName, signature);
130    }
131
132    public RRSIG(TYPE typeCovered, SignatureAlgorithm algorithm, byte labels,
133            long originalTtl, Date signatureExpiration, Date signatureInception,
134            int keyTag, String signerName, byte[] signature) {
135        this(typeCovered, algorithm.number, labels, originalTtl, signatureExpiration, signatureInception,
136                keyTag, DnsName.from(signerName), signature);
137    }
138
139    public byte[] getSignature() {
140        return signature.clone();
141    }
142
143    public DataInputStream getSignatureAsDataInputStream() {
144        return new DataInputStream(new ByteArrayInputStream(signature));
145    }
146
147    public int getSignatureLength() {
148        return signature.length;
149    }
150
151    private transient String base64SignatureCache;
152
153    public String getSignatureBase64() {
154        if (base64SignatureCache == null) {
155            base64SignatureCache = Base64.encodeToString(signature);
156        }
157        return base64SignatureCache;
158    }
159
160    @Override
161    public TYPE getType() {
162        return TYPE.RRSIG;
163    }
164
165    @Override
166    public void serialize(DataOutputStream dos) throws IOException {
167        writePartialSignature(dos);
168        dos.write(signature);
169    }
170
171    @SuppressWarnings("JavaUtilDate")
172    public void writePartialSignature(DataOutputStream dos) throws IOException {
173        dos.writeShort(typeCovered.getValue());
174        dos.writeByte(algorithmByte);
175        dos.writeByte(labels);
176        dos.writeInt((int) originalTtl);
177        dos.writeInt((int) (signatureExpiration.getTime() / 1000));
178        dos.writeInt((int) (signatureInception.getTime() / 1000));
179        dos.writeShort(keyTag);
180        signerName.writeToStream(dos);
181    }
182
183    @Override
184    public String toString() {
185        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
186        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
187        StringBuilder sb = new StringBuilder()
188                .append(typeCovered).append(' ')
189                .append(algorithm).append(' ')
190                .append(labels).append(' ')
191                .append(originalTtl).append(' ')
192                .append(dateFormat.format(signatureExpiration)).append(' ')
193                .append(dateFormat.format(signatureInception)).append(' ')
194                .append(keyTag).append(' ')
195                .append(signerName).append(". ")
196                .append(getSignatureBase64());
197        return sb.toString();
198    }
199}