1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/browserid/DSACryptoImplementation.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,244 @@ 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.DSAParams; 1.18 +import java.security.interfaces.DSAPrivateKey; 1.19 +import java.security.interfaces.DSAPublicKey; 1.20 +import java.security.spec.DSAPrivateKeySpec; 1.21 +import java.security.spec.DSAPublicKeySpec; 1.22 +import java.security.spec.InvalidKeySpecException; 1.23 +import java.security.spec.KeySpec; 1.24 + 1.25 +import org.mozilla.gecko.sync.ExtendedJSONObject; 1.26 +import org.mozilla.gecko.sync.NonObjectJSONException; 1.27 +import org.mozilla.gecko.sync.Utils; 1.28 + 1.29 +public class DSACryptoImplementation { 1.30 + public static final String SIGNATURE_ALGORITHM = "SHA1withDSA"; 1.31 + public static final int SIGNATURE_LENGTH_BYTES = 40; // DSA signatures are always 40 bytes long. 1.32 + 1.33 + /** 1.34 + * Parameters are serialized as hex strings. Hex-versus-decimal was 1.35 + * reverse-engineered from what the Persona public verifier accepted. We 1.36 + * expect to follow the JOSE/JWT spec as it solidifies, and that will probably 1.37 + * mean unifying this base. 1.38 + */ 1.39 + protected static final int SERIALIZATION_BASE = 16; 1.40 + 1.41 + protected static class DSAVerifyingPublicKey implements VerifyingPublicKey { 1.42 + protected final DSAPublicKey publicKey; 1.43 + 1.44 + public DSAVerifyingPublicKey(DSAPublicKey publicKey) { 1.45 + this.publicKey = publicKey; 1.46 + } 1.47 + 1.48 + /** 1.49 + * Serialize to a JSON object. 1.50 + * <p> 1.51 + * Parameters are serialized as hex strings. Hex-versus-decimal was 1.52 + * reverse-engineered from what the Persona public verifier accepted. 1.53 + */ 1.54 + @Override 1.55 + public ExtendedJSONObject toJSONObject() { 1.56 + DSAParams params = publicKey.getParams(); 1.57 + ExtendedJSONObject o = new ExtendedJSONObject(); 1.58 + o.put("algorithm", "DS"); 1.59 + o.put("y", publicKey.getY().toString(SERIALIZATION_BASE)); 1.60 + o.put("g", params.getG().toString(SERIALIZATION_BASE)); 1.61 + o.put("p", params.getP().toString(SERIALIZATION_BASE)); 1.62 + o.put("q", params.getQ().toString(SERIALIZATION_BASE)); 1.63 + return o; 1.64 + } 1.65 + 1.66 + @Override 1.67 + public boolean verifyMessage(byte[] bytes, byte[] signature) 1.68 + throws GeneralSecurityException { 1.69 + if (bytes == null) { 1.70 + throw new IllegalArgumentException("bytes must not be null"); 1.71 + } 1.72 + if (signature == null) { 1.73 + throw new IllegalArgumentException("signature must not be null"); 1.74 + } 1.75 + if (signature.length != SIGNATURE_LENGTH_BYTES) { 1.76 + return false; 1.77 + } 1.78 + byte[] first = new byte[signature.length / 2]; 1.79 + byte[] second = new byte[signature.length / 2]; 1.80 + System.arraycopy(signature, 0, first, 0, first.length); 1.81 + System.arraycopy(signature, first.length, second, 0, second.length); 1.82 + BigInteger r = new BigInteger(Utils.byte2Hex(first), 16); 1.83 + BigInteger s = new BigInteger(Utils.byte2Hex(second), 16); 1.84 + // This is awful, but encoding an extra 0 byte works better on devices. 1.85 + byte[] encoded = ASNUtils.encodeTwoArraysToASN1( 1.86 + Utils.hex2Byte(r.toString(16), 1 + SIGNATURE_LENGTH_BYTES / 2), 1.87 + Utils.hex2Byte(s.toString(16), 1 + SIGNATURE_LENGTH_BYTES / 2)); 1.88 + 1.89 + final Signature signer = Signature.getInstance(SIGNATURE_ALGORITHM); 1.90 + signer.initVerify(publicKey); 1.91 + signer.update(bytes); 1.92 + return signer.verify(encoded); 1.93 + } 1.94 + } 1.95 + 1.96 + protected static class DSASigningPrivateKey implements SigningPrivateKey { 1.97 + protected final DSAPrivateKey privateKey; 1.98 + 1.99 + public DSASigningPrivateKey(DSAPrivateKey privateKey) { 1.100 + this.privateKey = privateKey; 1.101 + } 1.102 + 1.103 + @Override 1.104 + public String getAlgorithm() { 1.105 + return "DS" + (privateKey.getParams().getP().bitLength() + 7)/8; 1.106 + } 1.107 + 1.108 + /** 1.109 + * Serialize to a JSON object. 1.110 + * <p> 1.111 + * Parameters are serialized as decimal strings. Hex-versus-decimal was 1.112 + * reverse-engineered from what the Persona public verifier accepted. 1.113 + */ 1.114 + @Override 1.115 + public ExtendedJSONObject toJSONObject() { 1.116 + DSAParams params = privateKey.getParams(); 1.117 + ExtendedJSONObject o = new ExtendedJSONObject(); 1.118 + o.put("algorithm", "DS"); 1.119 + o.put("x", privateKey.getX().toString(SERIALIZATION_BASE)); 1.120 + o.put("g", params.getG().toString(SERIALIZATION_BASE)); 1.121 + o.put("p", params.getP().toString(SERIALIZATION_BASE)); 1.122 + o.put("q", params.getQ().toString(SERIALIZATION_BASE)); 1.123 + return o; 1.124 + } 1.125 + 1.126 + @Override 1.127 + public byte[] signMessage(byte[] bytes) 1.128 + throws GeneralSecurityException { 1.129 + if (bytes == null) { 1.130 + throw new IllegalArgumentException("bytes must not be null"); 1.131 + } 1.132 + final Signature signer = Signature.getInstance(SIGNATURE_ALGORITHM); 1.133 + signer.initSign(privateKey); 1.134 + signer.update(bytes); 1.135 + final byte[] signature = signer.sign(); 1.136 + 1.137 + final byte[][] arrays = ASNUtils.decodeTwoArraysFromASN1(signature); 1.138 + BigInteger r = new BigInteger(arrays[0]); 1.139 + BigInteger s = new BigInteger(arrays[1]); 1.140 + // This is awful, but signatures are always 40 bytes long. 1.141 + byte[] decoded = Utils.concatAll( 1.142 + Utils.hex2Byte(r.toString(16), SIGNATURE_LENGTH_BYTES / 2), 1.143 + Utils.hex2Byte(s.toString(16), SIGNATURE_LENGTH_BYTES / 2)); 1.144 + return decoded; 1.145 + } 1.146 + } 1.147 + 1.148 + public static BrowserIDKeyPair generateKeyPair(int keysize) 1.149 + throws NoSuchAlgorithmException { 1.150 + final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); 1.151 + keyPairGenerator.initialize(keysize); 1.152 + final KeyPair keyPair = keyPairGenerator.generateKeyPair(); 1.153 + DSAPrivateKey privateKey = (DSAPrivateKey) keyPair.getPrivate(); 1.154 + DSAPublicKey publicKey = (DSAPublicKey) keyPair.getPublic(); 1.155 + return new BrowserIDKeyPair(new DSASigningPrivateKey(privateKey), new DSAVerifyingPublicKey(publicKey)); 1.156 + } 1.157 + 1.158 + public static SigningPrivateKey createPrivateKey(BigInteger x, BigInteger p, BigInteger q, BigInteger g) throws NoSuchAlgorithmException, InvalidKeySpecException { 1.159 + if (x == null) { 1.160 + throw new IllegalArgumentException("x must not be null"); 1.161 + } 1.162 + if (p == null) { 1.163 + throw new IllegalArgumentException("p must not be null"); 1.164 + } 1.165 + if (q == null) { 1.166 + throw new IllegalArgumentException("q must not be null"); 1.167 + } 1.168 + if (g == null) { 1.169 + throw new IllegalArgumentException("g must not be null"); 1.170 + } 1.171 + KeySpec keySpec = new DSAPrivateKeySpec(x, p, q, g); 1.172 + KeyFactory keyFactory = KeyFactory.getInstance("DSA"); 1.173 + DSAPrivateKey privateKey = (DSAPrivateKey) keyFactory.generatePrivate(keySpec); 1.174 + return new DSASigningPrivateKey(privateKey); 1.175 + } 1.176 + 1.177 + public static VerifyingPublicKey createPublicKey(BigInteger y, BigInteger p, BigInteger q, BigInteger g) throws NoSuchAlgorithmException, InvalidKeySpecException { 1.178 + if (y == null) { 1.179 + throw new IllegalArgumentException("n must not be null"); 1.180 + } 1.181 + if (p == null) { 1.182 + throw new IllegalArgumentException("p must not be null"); 1.183 + } 1.184 + if (q == null) { 1.185 + throw new IllegalArgumentException("q must not be null"); 1.186 + } 1.187 + if (g == null) { 1.188 + throw new IllegalArgumentException("g must not be null"); 1.189 + } 1.190 + KeySpec keySpec = new DSAPublicKeySpec(y, p, q, g); 1.191 + KeyFactory keyFactory = KeyFactory.getInstance("DSA"); 1.192 + DSAPublicKey publicKey = (DSAPublicKey) keyFactory.generatePublic(keySpec); 1.193 + return new DSAVerifyingPublicKey(publicKey); 1.194 + } 1.195 + 1.196 + public static SigningPrivateKey createPrivateKey(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException { 1.197 + String algorithm = o.getString("algorithm"); 1.198 + if (!"DS".equals(algorithm)) { 1.199 + throw new InvalidKeySpecException("algorithm must equal DS, was " + algorithm); 1.200 + } 1.201 + try { 1.202 + BigInteger x = new BigInteger(o.getString("x"), SERIALIZATION_BASE); 1.203 + BigInteger p = new BigInteger(o.getString("p"), SERIALIZATION_BASE); 1.204 + BigInteger q = new BigInteger(o.getString("q"), SERIALIZATION_BASE); 1.205 + BigInteger g = new BigInteger(o.getString("g"), SERIALIZATION_BASE); 1.206 + return createPrivateKey(x, p, q, g); 1.207 + } catch (NullPointerException e) { 1.208 + throw new InvalidKeySpecException("x, p, q, and g must be integers encoded as strings, base " + SERIALIZATION_BASE); 1.209 + } catch (NumberFormatException e) { 1.210 + throw new InvalidKeySpecException("x, p, q, and g must be integers encoded as strings, base " + SERIALIZATION_BASE); 1.211 + } 1.212 + } 1.213 + 1.214 + public static VerifyingPublicKey createPublicKey(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException { 1.215 + String algorithm = o.getString("algorithm"); 1.216 + if (!"DS".equals(algorithm)) { 1.217 + throw new InvalidKeySpecException("algorithm must equal DS, was " + algorithm); 1.218 + } 1.219 + try { 1.220 + BigInteger y = new BigInteger(o.getString("y"), SERIALIZATION_BASE); 1.221 + BigInteger p = new BigInteger(o.getString("p"), SERIALIZATION_BASE); 1.222 + BigInteger q = new BigInteger(o.getString("q"), SERIALIZATION_BASE); 1.223 + BigInteger g = new BigInteger(o.getString("g"), SERIALIZATION_BASE); 1.224 + return createPublicKey(y, p, q, g); 1.225 + } catch (NullPointerException e) { 1.226 + throw new InvalidKeySpecException("y, p, q, and g must be integers encoded as strings, base " + SERIALIZATION_BASE); 1.227 + } catch (NumberFormatException e) { 1.228 + throw new InvalidKeySpecException("y, p, q, and g must be integers encoded as strings, base " + SERIALIZATION_BASE); 1.229 + } 1.230 + } 1.231 + 1.232 + public static BrowserIDKeyPair fromJSONObject(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException { 1.233 + try { 1.234 + ExtendedJSONObject privateKey = o.getObject(BrowserIDKeyPair.JSON_KEY_PRIVATEKEY); 1.235 + ExtendedJSONObject publicKey = o.getObject(BrowserIDKeyPair.JSON_KEY_PUBLICKEY); 1.236 + if (privateKey == null) { 1.237 + throw new InvalidKeySpecException("privateKey must not be null"); 1.238 + } 1.239 + if (publicKey == null) { 1.240 + throw new InvalidKeySpecException("publicKey must not be null"); 1.241 + } 1.242 + return new BrowserIDKeyPair(createPrivateKey(privateKey), createPublicKey(publicKey)); 1.243 + } catch (NonObjectJSONException e) { 1.244 + throw new InvalidKeySpecException("privateKey and publicKey must be JSON objects"); 1.245 + } 1.246 + } 1.247 +}