|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 package org.mozilla.gecko.browserid; |
|
6 |
|
7 import java.math.BigInteger; |
|
8 import java.security.GeneralSecurityException; |
|
9 import java.security.KeyFactory; |
|
10 import java.security.KeyPair; |
|
11 import java.security.KeyPairGenerator; |
|
12 import java.security.NoSuchAlgorithmException; |
|
13 import java.security.Signature; |
|
14 import java.security.interfaces.RSAPrivateKey; |
|
15 import java.security.interfaces.RSAPublicKey; |
|
16 import java.security.spec.InvalidKeySpecException; |
|
17 import java.security.spec.KeySpec; |
|
18 import java.security.spec.RSAPrivateKeySpec; |
|
19 import java.security.spec.RSAPublicKeySpec; |
|
20 |
|
21 import org.mozilla.gecko.sync.ExtendedJSONObject; |
|
22 import org.mozilla.gecko.sync.NonObjectJSONException; |
|
23 |
|
24 public class RSACryptoImplementation { |
|
25 public static final String SIGNATURE_ALGORITHM = "SHA256withRSA"; |
|
26 |
|
27 /** |
|
28 * Parameters are serialized as decimal strings. Hex-versus-decimal was |
|
29 * reverse-engineered from what the Persona public verifier accepted. We |
|
30 * expect to follow the JOSE/JWT spec as it solidifies, and that will probably |
|
31 * mean unifying this base. |
|
32 */ |
|
33 protected static final int SERIALIZATION_BASE = 10; |
|
34 |
|
35 protected static class RSAVerifyingPublicKey implements VerifyingPublicKey { |
|
36 protected final RSAPublicKey publicKey; |
|
37 |
|
38 public RSAVerifyingPublicKey(RSAPublicKey publicKey) { |
|
39 this.publicKey = publicKey; |
|
40 } |
|
41 |
|
42 /** |
|
43 * Serialize to a JSON object. |
|
44 * <p> |
|
45 * Parameters are serialized as decimal strings. Hex-versus-decimal was |
|
46 * reverse-engineered from what the Persona public verifier accepted. |
|
47 */ |
|
48 @Override |
|
49 public ExtendedJSONObject toJSONObject() { |
|
50 ExtendedJSONObject o = new ExtendedJSONObject(); |
|
51 o.put("algorithm", "RS"); |
|
52 o.put("n", publicKey.getModulus().toString(SERIALIZATION_BASE)); |
|
53 o.put("e", publicKey.getPublicExponent().toString(SERIALIZATION_BASE)); |
|
54 return o; |
|
55 } |
|
56 |
|
57 @Override |
|
58 public boolean verifyMessage(byte[] bytes, byte[] signature) |
|
59 throws GeneralSecurityException { |
|
60 final Signature signer = Signature.getInstance(SIGNATURE_ALGORITHM); |
|
61 signer.initVerify(publicKey); |
|
62 signer.update(bytes); |
|
63 return signer.verify(signature); |
|
64 } |
|
65 } |
|
66 |
|
67 protected static class RSASigningPrivateKey implements SigningPrivateKey { |
|
68 protected final RSAPrivateKey privateKey; |
|
69 |
|
70 public RSASigningPrivateKey(RSAPrivateKey privateKey) { |
|
71 this.privateKey = privateKey; |
|
72 } |
|
73 |
|
74 @Override |
|
75 public String getAlgorithm() { |
|
76 return "RS" + (privateKey.getModulus().bitLength() + 7)/8; |
|
77 } |
|
78 |
|
79 /** |
|
80 * Serialize to a JSON object. |
|
81 * <p> |
|
82 * Parameters are serialized as decimal strings. Hex-versus-decimal was |
|
83 * reverse-engineered from what the Persona public verifier accepted. |
|
84 */ |
|
85 @Override |
|
86 public ExtendedJSONObject toJSONObject() { |
|
87 ExtendedJSONObject o = new ExtendedJSONObject(); |
|
88 o.put("algorithm", "RS"); |
|
89 o.put("n", privateKey.getModulus().toString(SERIALIZATION_BASE)); |
|
90 o.put("d", privateKey.getPrivateExponent().toString(SERIALIZATION_BASE)); |
|
91 return o; |
|
92 } |
|
93 |
|
94 @Override |
|
95 public byte[] signMessage(byte[] bytes) |
|
96 throws GeneralSecurityException { |
|
97 final Signature signer = Signature.getInstance(SIGNATURE_ALGORITHM); |
|
98 signer.initSign(privateKey); |
|
99 signer.update(bytes); |
|
100 return signer.sign(); |
|
101 } |
|
102 } |
|
103 |
|
104 public static BrowserIDKeyPair generateKeyPair(final int keysize) throws NoSuchAlgorithmException { |
|
105 final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); |
|
106 keyPairGenerator.initialize(keysize); |
|
107 final KeyPair keyPair = keyPairGenerator.generateKeyPair(); |
|
108 RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); |
|
109 RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); |
|
110 return new BrowserIDKeyPair(new RSASigningPrivateKey(privateKey), new RSAVerifyingPublicKey(publicKey)); |
|
111 } |
|
112 |
|
113 public static SigningPrivateKey createPrivateKey(BigInteger n, BigInteger d) throws NoSuchAlgorithmException, InvalidKeySpecException { |
|
114 if (n == null) { |
|
115 throw new IllegalArgumentException("n must not be null"); |
|
116 } |
|
117 if (d == null) { |
|
118 throw new IllegalArgumentException("d must not be null"); |
|
119 } |
|
120 KeyFactory keyFactory = KeyFactory.getInstance("RSA"); |
|
121 KeySpec keySpec = new RSAPrivateKeySpec(n, d); |
|
122 RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec); |
|
123 return new RSASigningPrivateKey(privateKey); |
|
124 } |
|
125 |
|
126 public static VerifyingPublicKey createPublicKey(BigInteger n, BigInteger e) throws NoSuchAlgorithmException, InvalidKeySpecException { |
|
127 if (n == null) { |
|
128 throw new IllegalArgumentException("n must not be null"); |
|
129 } |
|
130 if (e == null) { |
|
131 throw new IllegalArgumentException("e must not be null"); |
|
132 } |
|
133 KeyFactory keyFactory = KeyFactory.getInstance("RSA"); |
|
134 KeySpec keySpec = new RSAPublicKeySpec(n, e); |
|
135 RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec); |
|
136 return new RSAVerifyingPublicKey(publicKey); |
|
137 } |
|
138 |
|
139 public static SigningPrivateKey createPrivateKey(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException { |
|
140 String algorithm = o.getString("algorithm"); |
|
141 if (!"RS".equals(algorithm)) { |
|
142 throw new InvalidKeySpecException("algorithm must equal RS, was " + algorithm); |
|
143 } |
|
144 try { |
|
145 BigInteger n = new BigInteger(o.getString("n"), SERIALIZATION_BASE); |
|
146 BigInteger d = new BigInteger(o.getString("d"), SERIALIZATION_BASE); |
|
147 return createPrivateKey(n, d); |
|
148 } catch (NullPointerException e) { |
|
149 throw new InvalidKeySpecException("n and d must be integers encoded as strings, base " + SERIALIZATION_BASE); |
|
150 } catch (NumberFormatException e) { |
|
151 throw new InvalidKeySpecException("n and d must be integers encoded as strings, base " + SERIALIZATION_BASE); |
|
152 } |
|
153 } |
|
154 |
|
155 public static VerifyingPublicKey createPublicKey(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException { |
|
156 String algorithm = o.getString("algorithm"); |
|
157 if (!"RS".equals(algorithm)) { |
|
158 throw new InvalidKeySpecException("algorithm must equal RS, was " + algorithm); |
|
159 } |
|
160 try { |
|
161 BigInteger n = new BigInteger(o.getString("n"), SERIALIZATION_BASE); |
|
162 BigInteger e = new BigInteger(o.getString("e"), SERIALIZATION_BASE); |
|
163 return createPublicKey(n, e); |
|
164 } catch (NullPointerException e) { |
|
165 throw new InvalidKeySpecException("n and e must be integers encoded as strings, base " + SERIALIZATION_BASE); |
|
166 } catch (NumberFormatException e) { |
|
167 throw new InvalidKeySpecException("n and e must be integers encoded as strings, base " + SERIALIZATION_BASE); |
|
168 } |
|
169 } |
|
170 |
|
171 public static BrowserIDKeyPair fromJSONObject(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException { |
|
172 try { |
|
173 ExtendedJSONObject privateKey = o.getObject(BrowserIDKeyPair.JSON_KEY_PRIVATEKEY); |
|
174 ExtendedJSONObject publicKey = o.getObject(BrowserIDKeyPair.JSON_KEY_PUBLICKEY); |
|
175 if (privateKey == null) { |
|
176 throw new InvalidKeySpecException("privateKey must not be null"); |
|
177 } |
|
178 if (publicKey == null) { |
|
179 throw new InvalidKeySpecException("publicKey must not be null"); |
|
180 } |
|
181 return new BrowserIDKeyPair(createPrivateKey(privateKey), createPublicKey(publicKey)); |
|
182 } catch (NonObjectJSONException e) { |
|
183 throw new InvalidKeySpecException("privateKey and publicKey must be JSON objects"); |
|
184 } |
|
185 } |
|
186 } |