11package com .amazonaws .encryptionsdk .internal ;
22
3+ import java .math .BigInteger ;
4+ import java .security .AlgorithmParameters ;
35import java .security .GeneralSecurityException ;
6+ import java .security .KeyFactory ;
47import java .security .KeyPair ;
58import java .security .KeyPairGenerator ;
9+ import java .security .NoSuchAlgorithmException ;
610import java .security .PublicKey ;
7-
8- import org . bouncycastle . crypto . params . ECDomainParameters ;
9- import org . bouncycastle . crypto . params . ECPublicKeyParameters ;
10- import org . bouncycastle . jcajce . provider . asymmetric . ec . BCECPublicKey ;
11- import org . bouncycastle . jce . ECNamedCurveTable ;
12- import org . bouncycastle . jce . interfaces . ECPublicKey ;
13- import org . bouncycastle . jce . provider . BouncyCastleProvider ;
14- import org . bouncycastle . jce . spec .ECNamedCurveParameterSpec ;
15- import org . bouncycastle . math . ec . ECPoint ;
11+ import java . security . interfaces . ECPublicKey ;
12+ import java . security . spec . ECFieldFp ;
13+ import java . security . spec . ECGenParameterSpec ;
14+ import java . security . spec . ECParameterSpec ;
15+ import java . security . spec . ECPoint ;
16+ import java . security . spec . ECPublicKeySpec ;
17+ import java . security . spec . InvalidKeySpecException ;
18+ import java . security . spec .InvalidParameterSpecException ;
19+ import java . util . Arrays ;
1620
1721import com .amazonaws .encryptionsdk .CryptoAlgorithm ;
1822
19- import static com .amazonaws .encryptionsdk .internal .BouncyCastleConfiguration .INTERNAL_BOUNCY_CASTLE_PROVIDER ;
23+ import static com .amazonaws .encryptionsdk .internal .Utils .bigIntegerToByteArray ;
24+ import static com .amazonaws .encryptionsdk .internal .Utils .encodeBase64String ;
25+ import static java .math .BigInteger .ONE ;
26+ import static java .math .BigInteger .ZERO ;
27+ import static org .apache .commons .lang3 .Validate .isInstanceOf ;
28+ import static org .apache .commons .lang3 .Validate .notNull ;
2029
2130/**
2231 * Provides a consistent interface across various trailing signature algorithms.
@@ -36,15 +45,36 @@ private TrailingSignatureAlgorithm() {
3645 public abstract String serializePublicKey (PublicKey key );
3746 public abstract KeyPair generateKey () throws GeneralSecurityException ;
3847
48+ /* Standards for Efficient Cryptography over a prime field */
49+ private static final String SEC_PRIME_FIELD_PREFIX = "secp" ;
50+
3951 private static final class ECDSASignatureAlgorithm extends TrailingSignatureAlgorithm {
40- private final ECNamedCurveParameterSpec ecSpec ;
52+ private final ECGenParameterSpec ecSpec ;
53+ private final ECParameterSpec ecParameterSpec ;
4154 private final String messageDigestAlgorithm ;
4255 private final String hashAndSignAlgorithm ;
56+ private static final String ELLIPTIC_CURVE_ALGORITHM = "EC" ;
57+ /* Constants used by SEC-1 v2 point compression and decompression algorithms */
58+ private static final BigInteger TWO = BigInteger .valueOf (2 );
59+ private static final BigInteger THREE = BigInteger .valueOf (3 );
60+ private static final BigInteger FOUR = BigInteger .valueOf (4 );
61+
62+ private ECDSASignatureAlgorithm (ECGenParameterSpec ecSpec , String messageDigestAlgorithm ) {
63+ if (!ecSpec .getName ().startsWith (SEC_PRIME_FIELD_PREFIX )) {
64+ throw new IllegalStateException ("Non-prime curves are not supported at this time" );
65+ }
4366
44- private ECDSASignatureAlgorithm (ECNamedCurveParameterSpec ecSpec , String messageDigestAlgorithm ) {
4567 this .ecSpec = ecSpec ;
4668 this .messageDigestAlgorithm = messageDigestAlgorithm ;
4769 this .hashAndSignAlgorithm = messageDigestAlgorithm + "withECDSA" ;
70+
71+ try {
72+ final AlgorithmParameters parameters = AlgorithmParameters .getInstance (ELLIPTIC_CURVE_ALGORITHM );
73+ parameters .init (ecSpec );
74+ this .ecParameterSpec = parameters .getParameterSpec (ECParameterSpec .class );
75+ } catch (NoSuchAlgorithmException | InvalidParameterSpecException e ) {
76+ throw new IllegalStateException ("Invalid algorithm" , e );
77+ }
4878 }
4979
5080 @ Override
@@ -62,41 +92,107 @@ public String getRawSignatureAlgorithm() {
6292 return "NONEwithECDSA" ;
6393 }
6494
65- @ Override public String getHashAndSignAlgorithm () {
95+ @ Override
96+ public String getHashAndSignAlgorithm () {
6697 return hashAndSignAlgorithm ;
6798 }
6899
100+ /**
101+ * Decodes a compressed elliptic curve point as described in SEC-1 v2 section 2.3.4
102+ *
103+ * @param keyString The serialized and compressed public key
104+ * @return The PublicKey
105+ * @see <a href="http://www.secg.org/sec1-v2.pdf">http://www.secg.org/sec1-v2.pdf</a>
106+ */
69107 @ Override
70108 public PublicKey deserializePublicKey (String keyString ) {
71- final ECPoint q = ecSpec .getCurve ().decodePoint (Utils .decodeBase64String (keyString ));
72-
73- ECPublicKeyParameters keyParams = new ECPublicKeyParameters (
74- q ,
75- new ECDomainParameters (ecSpec .getCurve (), ecSpec .getG (), ecSpec .getN (), ecSpec .getH ())
76- );
77-
78- return new BCECPublicKey ("EC" , keyParams , ecSpec , BouncyCastleProvider .CONFIGURATION );
109+ notNull (keyString , "keyString is required" );
110+
111+ final byte [] decodedKey = Utils .decodeBase64String (keyString );
112+ final BigInteger x = new BigInteger (1 , Arrays .copyOfRange (decodedKey , 1 , decodedKey .length ));
113+
114+ final byte compressedY = decodedKey [0 ];
115+ final BigInteger yOrder ;
116+
117+ if (compressedY == TWO .byteValue ()) {
118+ yOrder = ZERO ;
119+ } else if (compressedY == THREE .byteValue ()) {
120+ yOrder = ONE ;
121+ } else {
122+ throw new IllegalArgumentException ("Compressed y value was invalid" );
123+ }
124+
125+ final BigInteger p = ((ECFieldFp ) ecParameterSpec .getCurve ().getField ()).getP ();
126+ final BigInteger a = ecParameterSpec .getCurve ().getA ();
127+ final BigInteger b = ecParameterSpec .getCurve ().getB ();
128+
129+ //alpha must be equal to y^2, this is validated below
130+ final BigInteger alpha = x .modPow (THREE , p )
131+ .add (a .multiply (x ).mod (p ))
132+ .add (b )
133+ .mod (p );
134+
135+ final BigInteger beta ;
136+ if (p .mod (FOUR ).equals (THREE )) {
137+ beta = alpha .modPow (p .add (ONE ).divide (FOUR ), p );
138+ } else {
139+ throw new IllegalArgumentException ("Curve not supported at this time" );
140+ }
141+
142+ final BigInteger y = beta .mod (TWO ).equals (yOrder ) ? beta : p .subtract (beta );
143+
144+ //Validate that Y is a root of Y^2 to prevent invalid point attacks
145+ if (!alpha .equals (y .modPow (TWO , p ))) {
146+ throw new IllegalArgumentException ("Y was invalid" );
147+ }
148+
149+ try {
150+ return KeyFactory .getInstance (ELLIPTIC_CURVE_ALGORITHM ).generatePublic (
151+ new ECPublicKeySpec (new ECPoint (x , y ), ecParameterSpec ));
152+ } catch (InvalidKeySpecException | NoSuchAlgorithmException e ) {
153+ throw new IllegalStateException ("Invalid algorithm" , e );
154+ }
79155 }
80156
157+ /**
158+ * Encodes a compressed elliptic curve point as described in SEC-1 v2 section 2.3.3
159+ *
160+ * @param key The Elliptic Curve public key to compress and serialize
161+ * @return The serialized and compressed public key
162+ * @see <a href="http://www.secg.org/sec1-v2.pdf">http://www.secg.org/sec1-v2.pdf</a>
163+ */
81164 @ Override
82165 public String serializePublicKey (PublicKey key ) {
83- return Utils .encodeBase64String (((ECPublicKey )key ).getQ ().getEncoded (true ));
166+ notNull (key , "key is required" );
167+ isInstanceOf (ECPublicKey .class , key , "key must be an instance of ECPublicKey" );
168+
169+ final BigInteger x = ((ECPublicKey ) key ).getW ().getAffineX ();
170+ final BigInteger y = ((ECPublicKey ) key ).getW ().getAffineY ();
171+ final BigInteger compressedY = y .mod (TWO ).equals (ZERO ) ? TWO : THREE ;
172+
173+ final byte [] xBytes = bigIntegerToByteArray (x ,
174+ ecParameterSpec .getCurve ().getField ().getFieldSize () / Byte .SIZE );
175+
176+ final byte [] compressedKey = new byte [xBytes .length + 1 ];
177+ System .arraycopy (xBytes , 0 , compressedKey , 1 , xBytes .length );
178+ compressedKey [0 ] = compressedY .byteValue ();
179+
180+ return encodeBase64String (compressedKey );
84181 }
85182
86183 @ Override
87184 public KeyPair generateKey () throws GeneralSecurityException {
88- // We use BouncyCastle for this so that we can easily serialize the compressed point.
89- KeyPairGenerator keyGen = KeyPairGenerator .getInstance ("EC" , INTERNAL_BOUNCY_CASTLE_PROVIDER );
185+ KeyPairGenerator keyGen = KeyPairGenerator .getInstance (ELLIPTIC_CURVE_ALGORITHM );
90186 keyGen .initialize (ecSpec , Utils .getSecureRandom ());
91187
92188 return keyGen .generateKeyPair ();
93189 }
94190 }
95191
96192 private static final ECDSASignatureAlgorithm SHA256_ECDSA_P256
97- = new ECDSASignatureAlgorithm (ECNamedCurveTable . getParameterSpec ( "secp256r1 " ), "SHA256" );
193+ = new ECDSASignatureAlgorithm (new ECGenParameterSpec ( SEC_PRIME_FIELD_PREFIX + "256r1 " ), "SHA256" );
98194 private static final ECDSASignatureAlgorithm SHA384_ECDSA_P384
99- = new ECDSASignatureAlgorithm (ECNamedCurveTable . getParameterSpec ( "secp384r1 " ), "SHA384" );
195+ = new ECDSASignatureAlgorithm (new ECGenParameterSpec ( SEC_PRIME_FIELD_PREFIX + "384r1 " ), "SHA384" );
100196
101197 public static TrailingSignatureAlgorithm forCryptoAlgorithm (CryptoAlgorithm algorithm ) {
102198 switch (algorithm ) {
0 commit comments