1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/browserid/RSACryptoImplementation.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,186 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +package org.mozilla.gecko.browserid; 1.9 + 1.10 +import java.math.BigInteger; 1.11 +import java.security.GeneralSecurityException; 1.12 +import java.security.KeyFactory; 1.13 +import java.security.KeyPair; 1.14 +import java.security.KeyPairGenerator; 1.15 +import java.security.NoSuchAlgorithmException; 1.16 +import java.security.Signature; 1.17 +import java.security.interfaces.RSAPrivateKey; 1.18 +import java.security.interfaces.RSAPublicKey; 1.19 +import java.security.spec.InvalidKeySpecException; 1.20 +import java.security.spec.KeySpec; 1.21 +import java.security.spec.RSAPrivateKeySpec; 1.22 +import java.security.spec.RSAPublicKeySpec; 1.23 + 1.24 +import org.mozilla.gecko.sync.ExtendedJSONObject; 1.25 +import org.mozilla.gecko.sync.NonObjectJSONException; 1.26 + 1.27 +public class RSACryptoImplementation { 1.28 + public static final String SIGNATURE_ALGORITHM = "SHA256withRSA"; 1.29 + 1.30 + /** 1.31 + * Parameters are serialized as decimal strings. Hex-versus-decimal was 1.32 + * reverse-engineered from what the Persona public verifier accepted. We 1.33 + * expect to follow the JOSE/JWT spec as it solidifies, and that will probably 1.34 + * mean unifying this base. 1.35 + */ 1.36 + protected static final int SERIALIZATION_BASE = 10; 1.37 + 1.38 + protected static class RSAVerifyingPublicKey implements VerifyingPublicKey { 1.39 + protected final RSAPublicKey publicKey; 1.40 + 1.41 + public RSAVerifyingPublicKey(RSAPublicKey publicKey) { 1.42 + this.publicKey = publicKey; 1.43 + } 1.44 + 1.45 + /** 1.46 + * Serialize to a JSON object. 1.47 + * <p> 1.48 + * Parameters are serialized as decimal strings. Hex-versus-decimal was 1.49 + * reverse-engineered from what the Persona public verifier accepted. 1.50 + */ 1.51 + @Override 1.52 + public ExtendedJSONObject toJSONObject() { 1.53 + ExtendedJSONObject o = new ExtendedJSONObject(); 1.54 + o.put("algorithm", "RS"); 1.55 + o.put("n", publicKey.getModulus().toString(SERIALIZATION_BASE)); 1.56 + o.put("e", publicKey.getPublicExponent().toString(SERIALIZATION_BASE)); 1.57 + return o; 1.58 + } 1.59 + 1.60 + @Override 1.61 + public boolean verifyMessage(byte[] bytes, byte[] signature) 1.62 + throws GeneralSecurityException { 1.63 + final Signature signer = Signature.getInstance(SIGNATURE_ALGORITHM); 1.64 + signer.initVerify(publicKey); 1.65 + signer.update(bytes); 1.66 + return signer.verify(signature); 1.67 + } 1.68 + } 1.69 + 1.70 + protected static class RSASigningPrivateKey implements SigningPrivateKey { 1.71 + protected final RSAPrivateKey privateKey; 1.72 + 1.73 + public RSASigningPrivateKey(RSAPrivateKey privateKey) { 1.74 + this.privateKey = privateKey; 1.75 + } 1.76 + 1.77 + @Override 1.78 + public String getAlgorithm() { 1.79 + return "RS" + (privateKey.getModulus().bitLength() + 7)/8; 1.80 + } 1.81 + 1.82 + /** 1.83 + * Serialize to a JSON object. 1.84 + * <p> 1.85 + * Parameters are serialized as decimal strings. Hex-versus-decimal was 1.86 + * reverse-engineered from what the Persona public verifier accepted. 1.87 + */ 1.88 + @Override 1.89 + public ExtendedJSONObject toJSONObject() { 1.90 + ExtendedJSONObject o = new ExtendedJSONObject(); 1.91 + o.put("algorithm", "RS"); 1.92 + o.put("n", privateKey.getModulus().toString(SERIALIZATION_BASE)); 1.93 + o.put("d", privateKey.getPrivateExponent().toString(SERIALIZATION_BASE)); 1.94 + return o; 1.95 + } 1.96 + 1.97 + @Override 1.98 + public byte[] signMessage(byte[] bytes) 1.99 + throws GeneralSecurityException { 1.100 + final Signature signer = Signature.getInstance(SIGNATURE_ALGORITHM); 1.101 + signer.initSign(privateKey); 1.102 + signer.update(bytes); 1.103 + return signer.sign(); 1.104 + } 1.105 + } 1.106 + 1.107 + public static BrowserIDKeyPair generateKeyPair(final int keysize) throws NoSuchAlgorithmException { 1.108 + final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); 1.109 + keyPairGenerator.initialize(keysize); 1.110 + final KeyPair keyPair = keyPairGenerator.generateKeyPair(); 1.111 + RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); 1.112 + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); 1.113 + return new BrowserIDKeyPair(new RSASigningPrivateKey(privateKey), new RSAVerifyingPublicKey(publicKey)); 1.114 + } 1.115 + 1.116 + public static SigningPrivateKey createPrivateKey(BigInteger n, BigInteger d) throws NoSuchAlgorithmException, InvalidKeySpecException { 1.117 + if (n == null) { 1.118 + throw new IllegalArgumentException("n must not be null"); 1.119 + } 1.120 + if (d == null) { 1.121 + throw new IllegalArgumentException("d must not be null"); 1.122 + } 1.123 + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 1.124 + KeySpec keySpec = new RSAPrivateKeySpec(n, d); 1.125 + RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec); 1.126 + return new RSASigningPrivateKey(privateKey); 1.127 + } 1.128 + 1.129 + public static VerifyingPublicKey createPublicKey(BigInteger n, BigInteger e) throws NoSuchAlgorithmException, InvalidKeySpecException { 1.130 + if (n == null) { 1.131 + throw new IllegalArgumentException("n must not be null"); 1.132 + } 1.133 + if (e == null) { 1.134 + throw new IllegalArgumentException("e must not be null"); 1.135 + } 1.136 + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 1.137 + KeySpec keySpec = new RSAPublicKeySpec(n, e); 1.138 + RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec); 1.139 + return new RSAVerifyingPublicKey(publicKey); 1.140 + } 1.141 + 1.142 + public static SigningPrivateKey createPrivateKey(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException { 1.143 + String algorithm = o.getString("algorithm"); 1.144 + if (!"RS".equals(algorithm)) { 1.145 + throw new InvalidKeySpecException("algorithm must equal RS, was " + algorithm); 1.146 + } 1.147 + try { 1.148 + BigInteger n = new BigInteger(o.getString("n"), SERIALIZATION_BASE); 1.149 + BigInteger d = new BigInteger(o.getString("d"), SERIALIZATION_BASE); 1.150 + return createPrivateKey(n, d); 1.151 + } catch (NullPointerException e) { 1.152 + throw new InvalidKeySpecException("n and d must be integers encoded as strings, base " + SERIALIZATION_BASE); 1.153 + } catch (NumberFormatException e) { 1.154 + throw new InvalidKeySpecException("n and d must be integers encoded as strings, base " + SERIALIZATION_BASE); 1.155 + } 1.156 + } 1.157 + 1.158 + public static VerifyingPublicKey createPublicKey(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException { 1.159 + String algorithm = o.getString("algorithm"); 1.160 + if (!"RS".equals(algorithm)) { 1.161 + throw new InvalidKeySpecException("algorithm must equal RS, was " + algorithm); 1.162 + } 1.163 + try { 1.164 + BigInteger n = new BigInteger(o.getString("n"), SERIALIZATION_BASE); 1.165 + BigInteger e = new BigInteger(o.getString("e"), SERIALIZATION_BASE); 1.166 + return createPublicKey(n, e); 1.167 + } catch (NullPointerException e) { 1.168 + throw new InvalidKeySpecException("n and e must be integers encoded as strings, base " + SERIALIZATION_BASE); 1.169 + } catch (NumberFormatException e) { 1.170 + throw new InvalidKeySpecException("n and e must be integers encoded as strings, base " + SERIALIZATION_BASE); 1.171 + } 1.172 + } 1.173 + 1.174 + public static BrowserIDKeyPair fromJSONObject(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException { 1.175 + try { 1.176 + ExtendedJSONObject privateKey = o.getObject(BrowserIDKeyPair.JSON_KEY_PRIVATEKEY); 1.177 + ExtendedJSONObject publicKey = o.getObject(BrowserIDKeyPair.JSON_KEY_PUBLICKEY); 1.178 + if (privateKey == null) { 1.179 + throw new InvalidKeySpecException("privateKey must not be null"); 1.180 + } 1.181 + if (publicKey == null) { 1.182 + throw new InvalidKeySpecException("publicKey must not be null"); 1.183 + } 1.184 + return new BrowserIDKeyPair(createPrivateKey(privateKey), createPublicKey(publicKey)); 1.185 + } catch (NonObjectJSONException e) { 1.186 + throw new InvalidKeySpecException("privateKey and publicKey must be JSON objects"); 1.187 + } 1.188 + } 1.189 +}