1
1
package com .amazonaws .encryptionsdk .internal ;
2
2
3
+ import java .math .BigInteger ;
4
+ import java .security .AlgorithmParameters ;
3
5
import java .security .GeneralSecurityException ;
6
+ import java .security .KeyFactory ;
4
7
import java .security .KeyPair ;
5
8
import java .security .KeyPairGenerator ;
9
+ import java .security .NoSuchAlgorithmException ;
6
10
import 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 ;
16
20
17
21
import com .amazonaws .encryptionsdk .CryptoAlgorithm ;
18
22
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 ;
20
29
21
30
/**
22
31
* Provides a consistent interface across various trailing signature algorithms.
@@ -36,15 +45,36 @@ private TrailingSignatureAlgorithm() {
36
45
public abstract String serializePublicKey (PublicKey key );
37
46
public abstract KeyPair generateKey () throws GeneralSecurityException ;
38
47
48
+ /* Standards for Efficient Cryptography over a prime field */
49
+ private static final String SEC_PRIME_FIELD_PREFIX = "secp" ;
50
+
39
51
private static final class ECDSASignatureAlgorithm extends TrailingSignatureAlgorithm {
40
- private final ECNamedCurveParameterSpec ecSpec ;
52
+ private final ECGenParameterSpec ecSpec ;
53
+ private final ECParameterSpec ecParameterSpec ;
41
54
private final String messageDigestAlgorithm ;
42
55
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
+ }
43
66
44
- private ECDSASignatureAlgorithm (ECNamedCurveParameterSpec ecSpec , String messageDigestAlgorithm ) {
45
67
this .ecSpec = ecSpec ;
46
68
this .messageDigestAlgorithm = messageDigestAlgorithm ;
47
69
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
+ }
48
78
}
49
79
50
80
@ Override
@@ -62,41 +92,107 @@ public String getRawSignatureAlgorithm() {
62
92
return "NONEwithECDSA" ;
63
93
}
64
94
65
- @ Override public String getHashAndSignAlgorithm () {
95
+ @ Override
96
+ public String getHashAndSignAlgorithm () {
66
97
return hashAndSignAlgorithm ;
67
98
}
68
99
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
+ */
69
107
@ Override
70
108
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
+ }
79
155
}
80
156
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
+ */
81
164
@ Override
82
165
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 );
84
181
}
85
182
86
183
@ Override
87
184
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 );
90
186
keyGen .initialize (ecSpec , Utils .getSecureRandom ());
91
187
92
188
return keyGen .generateKeyPair ();
93
189
}
94
190
}
95
191
96
192
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" );
98
194
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" );
100
196
101
197
public static TrailingSignatureAlgorithm forCryptoAlgorithm (CryptoAlgorithm algorithm ) {
102
198
switch (algorithm ) {
0 commit comments