001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2016 ForgeRock AS.
015 */
016
017package org.forgerock.json.jose.jws.handlers;
018
019import java.nio.ByteBuffer;
020import java.security.InvalidKeyException;
021import java.security.NoSuchAlgorithmException;
022import java.security.Signature;
023import java.security.SignatureException;
024import java.security.interfaces.ECKey;
025import java.security.interfaces.ECPrivateKey;
026import java.security.interfaces.ECPublicKey;
027import java.util.Arrays;
028
029import org.forgerock.json.jose.exceptions.JwsException;
030import org.forgerock.json.jose.exceptions.JwsSigningException;
031import org.forgerock.json.jose.jws.JwsAlgorithm;
032import org.forgerock.json.jose.jws.JwsAlgorithmType;
033import org.forgerock.json.jose.jws.SupportedEllipticCurve;
034import org.forgerock.json.jose.utils.DerUtils;
035import org.forgerock.json.jose.utils.Utils;
036import org.forgerock.util.Reject;
037
038/**
039 * Elliptic Curve Digital Signature Algorithm (ECDSA) signing and verification.
040 */
041public class ECDSASigningHandler implements SigningHandler {
042    private final ECPrivateKey signingKey;
043    private final ECPublicKey verificationKey;
044    private final SupportedEllipticCurve curve;
045
046    /**
047     * Constructs the ECDSA signing handler for signing only.
048     *
049     * @param signingKey the private key to use for signing. Must not be null.
050     */
051    public ECDSASigningHandler(final ECPrivateKey signingKey) {
052        this.signingKey = signingKey;
053        this.verificationKey = null;
054        this.curve = validateKey(signingKey);
055    }
056
057    /**
058     * Constructs the ECDSA signing handler for verification only.
059     *
060     * @param verificationKey the public key to use for verification. Must not be null.
061     */
062    public ECDSASigningHandler(final ECPublicKey verificationKey) {
063        this.signingKey = null;
064        this.verificationKey = verificationKey;
065        this.curve = validateKey(verificationKey);
066    }
067
068    @Override
069    public byte[] sign(final JwsAlgorithm algorithm, final String data) {
070        return sign(algorithm, data.getBytes(Utils.CHARSET));
071    }
072
073    @Override
074    public byte[] sign(final JwsAlgorithm algorithm, final byte[] data) {
075        validateAlgorithm(algorithm);
076
077        try {
078            final Signature signature = Signature.getInstance(algorithm.getAlgorithm());
079            signature.initSign(signingKey);
080            signature.update(data);
081            return derDecode(signature.sign(), curve.getSignatureSize());
082        } catch (SignatureException | InvalidKeyException e) {
083            throw new JwsSigningException(e);
084        } catch (NoSuchAlgorithmException e) {
085            throw new JwsSigningException("Unsupported Signing Algorithm, " + algorithm.getAlgorithm(), e);
086        }
087    }
088
089    @Override
090    public boolean verify(final JwsAlgorithm algorithm, final byte[] data, final byte[] signature) {
091        validateAlgorithm(algorithm);
092
093        try {
094            final Signature validator = Signature.getInstance(algorithm.getAlgorithm());
095            validator.initVerify(verificationKey);
096            validator.update(data);
097            return validator.verify(derEncode(signature));
098        } catch (SignatureException | InvalidKeyException e) {
099            throw new JwsSigningException(e);
100        } catch (NoSuchAlgorithmException e) {
101            throw new JwsSigningException("Unsupported Signing Algorithm, " + algorithm.getAlgorithm(), e);
102        }
103    }
104
105    private void validateAlgorithm(JwsAlgorithm algorithm) {
106        Reject.ifNull(algorithm, "Algorithm must not be null.");
107        Reject.ifTrue(algorithm.getAlgorithmType() != JwsAlgorithmType.ECDSA, "Not an ECDSA algorithm.");
108    }
109
110    /**
111     * Validate that the parameters of the key match the standard P-256 curve as required by the ES256 JWA standard.
112     * @param key the key to validate.
113     */
114    private SupportedEllipticCurve validateKey(final ECKey key) {
115        Reject.ifNull(key);
116        try {
117            return SupportedEllipticCurve.forKey(key);
118        } catch (IllegalArgumentException ex) {
119            throw new JwsException(ex);
120        }
121    }
122
123    /**
124     * Minimal DER decoder for the format returned by the SunEC signature provider.
125     */
126    private static byte[] derDecode(final byte[] signature, final int signatureSize) {
127        final ByteBuffer buffer = ByteBuffer.wrap(signature);
128        if (buffer.get() != DerUtils.SEQUENCE_TAG) {
129            throw new JwsSigningException("Unable to decode DER signature");
130        }
131        // Skip overall size
132        DerUtils.readLength(buffer);
133
134        final byte[] output = new byte[signatureSize];
135        final int componentSize = signatureSize >> 1;
136        DerUtils.readUnsignedInteger(buffer, output, 0, componentSize);
137        DerUtils.readUnsignedInteger(buffer, output, componentSize, componentSize);
138        return output;
139    }
140
141    /**
142     * Minimal DER encoder for the format expected by the SunEC signature provider.
143     */
144    private static byte[] derEncode(final byte[] signature) {
145        Reject.ifNull(signature);
146        SupportedEllipticCurve curve = SupportedEllipticCurve.forSignature(signature);
147
148        int midPoint = curve.getSignatureSize() >> 1;
149        final byte[] r = Arrays.copyOfRange(signature, 0, midPoint);
150        final byte[] s = Arrays.copyOfRange(signature, midPoint, signature.length);
151
152        // Each integer component needs at most 2 bytes for the length field and 1 byte for the tag, for a total of 6
153        // bytes for both integers.
154        final ByteBuffer params = ByteBuffer.allocate(signature.length + 6);
155        DerUtils.writeInteger(params, r);
156        DerUtils.writeInteger(params, s);
157
158        final int size = params.position();
159        // The overall sequence may need up to 4 bytes for the length field plus 1 byte for the sequence tag.
160        final ByteBuffer sequence = ByteBuffer.allocate(size + 6);
161        sequence.put(DerUtils.SEQUENCE_TAG);
162        DerUtils.writeLength(sequence, size);
163        sequence.put((ByteBuffer) params.flip());
164        sequence.flip();
165        final byte[] encodedSignature = new byte[sequence.remaining()];
166        sequence.get(encodedSignature);
167        return encodedSignature;
168    }
169
170}