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}