View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2016 ForgeRock AS.
15   */
16  
17  package org.forgerock.json.jose.jws.handlers;
18  
19  import java.nio.ByteBuffer;
20  import java.security.InvalidKeyException;
21  import java.security.NoSuchAlgorithmException;
22  import java.security.Signature;
23  import java.security.SignatureException;
24  import java.security.interfaces.ECKey;
25  import java.security.interfaces.ECPrivateKey;
26  import java.security.interfaces.ECPublicKey;
27  import java.util.Arrays;
28  
29  import org.forgerock.json.jose.exceptions.JwsException;
30  import org.forgerock.json.jose.exceptions.JwsSigningException;
31  import org.forgerock.json.jose.jws.JwsAlgorithm;
32  import org.forgerock.json.jose.jws.JwsAlgorithmType;
33  import org.forgerock.json.jose.jws.SupportedEllipticCurve;
34  import org.forgerock.json.jose.utils.DerUtils;
35  import org.forgerock.json.jose.utils.Utils;
36  import org.forgerock.util.Reject;
37  
38  /**
39   * Elliptic Curve Digital Signature Algorithm (ECDSA) signing and verification.
40   */
41  public class ECDSASigningHandler implements SigningHandler {
42      private final ECPrivateKey signingKey;
43      private final ECPublicKey verificationKey;
44      private final SupportedEllipticCurve curve;
45  
46      /**
47       * Constructs the ECDSA signing handler for signing only.
48       *
49       * @param signingKey the private key to use for signing. Must not be null.
50       */
51      public ECDSASigningHandler(final ECPrivateKey signingKey) {
52          this.signingKey = signingKey;
53          this.verificationKey = null;
54          this.curve = validateKey(signingKey);
55      }
56  
57      /**
58       * Constructs the ECDSA signing handler for verification only.
59       *
60       * @param verificationKey the public key to use for verification. Must not be null.
61       */
62      public ECDSASigningHandler(final ECPublicKey verificationKey) {
63          this.signingKey = null;
64          this.verificationKey = verificationKey;
65          this.curve = validateKey(verificationKey);
66      }
67  
68      @Override
69      public byte[] sign(final JwsAlgorithm algorithm, final String data) {
70          return sign(algorithm, data.getBytes(Utils.CHARSET));
71      }
72  
73      @Override
74      public byte[] sign(final JwsAlgorithm algorithm, final byte[] data) {
75          validateAlgorithm(algorithm);
76  
77          try {
78              final Signature signature = Signature.getInstance(algorithm.getAlgorithm());
79              signature.initSign(signingKey);
80              signature.update(data);
81              return derDecode(signature.sign(), curve.getSignatureSize());
82          } catch (SignatureException | InvalidKeyException e) {
83              throw new JwsSigningException(e);
84          } catch (NoSuchAlgorithmException e) {
85              throw new JwsSigningException("Unsupported Signing Algorithm, " + algorithm.getAlgorithm(), e);
86          }
87      }
88  
89      @Override
90      public boolean verify(final JwsAlgorithm algorithm, final byte[] data, final byte[] signature) {
91          validateAlgorithm(algorithm);
92  
93          try {
94              final Signature validator = Signature.getInstance(algorithm.getAlgorithm());
95              validator.initVerify(verificationKey);
96              validator.update(data);
97              return validator.verify(derEncode(signature));
98          } catch (SignatureException | InvalidKeyException e) {
99              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 }