mirror of https://github.com/apache/jclouds.git
missing file from last commit
This commit is contained in:
parent
45641d354a
commit
7dcc27a63d
|
@ -0,0 +1,255 @@
|
|||
package org.jclouds.crypto;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Predicates.notNull;
|
||||
import static com.google.common.base.Throwables.propagate;
|
||||
import static com.google.common.collect.Iterators.filter;
|
||||
import static com.google.common.io.ByteStreams.limit;
|
||||
import static com.google.common.io.ByteStreams.toByteArray;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.interfaces.RSAPrivateCrtKey;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.security.spec.RSAPrivateCrtKeySpec;
|
||||
import java.security.spec.RSAPublicKeySpec;
|
||||
import java.util.Iterator;
|
||||
/**
|
||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||
* contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. jclouds licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableList.Builder;
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
/**
|
||||
*
|
||||
* This codec is based on code from {@code bouncycastle.org}. It simplifies extraction of RSA keys from {@code ASN.1}
|
||||
* encoding. This only parses the {@code BigInteger} elements needed to generate {@link KeySpec}
|
||||
*
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
@Beta
|
||||
final class ASN1Codec {
|
||||
|
||||
private ASN1Codec() {
|
||||
};
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static RSAPublicKeySpec decodeRSAPublicKey(byte[] bytes) {
|
||||
List<Object> seq = createASN1Sequence(bytes);
|
||||
checkArgument(seq.size() == 2, "expected 2 components of ASN1Sequence: %s", seq);
|
||||
if (seq.get(1) instanceof List) {
|
||||
seq = List.class.cast(seq.get(1));
|
||||
}
|
||||
return new RSAPublicKeySpec(bigIntAt(seq, 0), bigIntAt(seq, 1));
|
||||
}
|
||||
|
||||
static RSAPrivateCrtKeySpec decodeRSAPrivateKey(byte[] bytes) {
|
||||
List<Object> seq = createASN1Sequence(bytes);
|
||||
checkArgument(seq.size() >= 9, "not enough elements (%s) for a private key", seq.size(), seq);
|
||||
int version = bigIntAt(seq, 0).intValue();
|
||||
checkArgument(version == 0 || version == 1, "wrong version %s for RSA private key", version);
|
||||
return new RSAPrivateCrtKeySpec(bigIntAt(seq, 1), bigIntAt(seq, 2), bigIntAt(seq, 3), bigIntAt(seq, 4), bigIntAt(
|
||||
seq, 5), bigIntAt(seq, 6), bigIntAt(seq, 7), bigIntAt(seq, 8));
|
||||
}
|
||||
|
||||
private static BigInteger bigIntAt(List<Object> seq, int index) {
|
||||
return BigInteger.class.cast(seq.get(index));
|
||||
}
|
||||
|
||||
private static final int TAG = 0x02;
|
||||
private static final int CONSTRUCTED = 0x20;
|
||||
private static final int INTEGER = 0x02;
|
||||
private static final int BIT_STRING = 0x03;
|
||||
private static final int SEQUENCE = 0x10;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<Object> createASN1Sequence(byte[] input) {
|
||||
Object out = create(new ByteArrayInputStream(input), input.length).get();
|
||||
checkArgument(out instanceof List, "expected List not %s", out);
|
||||
return List.class.cast(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* skips most {@code ASN.1} tags as we are only interested in the integers. As such, this either creates a
|
||||
* {@code BigInteger}, a list of them, or a nested list.
|
||||
*/
|
||||
private static Optional<Object> buildObject(int tag, int tagNo, InputStream in, int limit) {
|
||||
boolean isConstructed = (tag & CONSTRUCTED) != 0;
|
||||
InputStream limited = limit(in, limit);
|
||||
if (isConstructed && tagNo == SEQUENCE) {
|
||||
return Optional.<Object> of(buildEncodableList(limited, limit));
|
||||
}
|
||||
byte[] bytes = toArray(limited);
|
||||
switch (tagNo) {
|
||||
case BIT_STRING:
|
||||
return Optional.<Object> of(nestedASN1Sequence(bytes));
|
||||
case INTEGER:
|
||||
return Optional.<Object> of(new BigInteger(bytes));
|
||||
}
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
private static List<Object> nestedASN1Sequence(byte[] bytes) {
|
||||
checkArgument(bytes.length >= 1, "truncated BIT_STRING detected");
|
||||
byte[] data = new byte[bytes.length - 1];
|
||||
System.arraycopy(bytes, 1, data, 0, bytes.length - 1);
|
||||
return createASN1Sequence(data);
|
||||
}
|
||||
|
||||
private static List<Object> buildEncodableList(final InputStream in, final int limit) {
|
||||
return ImmutableList.copyOf(filter(new Iterator<Object>() {
|
||||
|
||||
boolean hasNext = true;
|
||||
|
||||
public boolean hasNext() {
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public Object next() {
|
||||
int tag = read(in);
|
||||
if (tag == -1) {
|
||||
hasNext = false;
|
||||
return null;
|
||||
}
|
||||
checkArgument(tag != 0, "invalid tag %s", tag);
|
||||
int tagNo = tag & 0x1f;
|
||||
int length = readLength(in, limit);
|
||||
checkArgument(length >= 0, "indefinite length not supported");
|
||||
return buildObject(tag, tagNo, in, length).orNull();
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
}
|
||||
|
||||
}, notNull()));
|
||||
}
|
||||
|
||||
private static Optional<Object> create(InputStream in, int limit) {
|
||||
int tag = read(in);
|
||||
if (tag == -1)
|
||||
return Optional.absent();
|
||||
checkArgument(tag != 0, "invalid tag %s", tag);
|
||||
int tagNo = tag & 0x1f;
|
||||
int length = readLength(in, limit);
|
||||
checkArgument(length >= 0, "indefinite length not supported");
|
||||
return buildObject(tag, tagNo, in, length);
|
||||
}
|
||||
|
||||
private static int readLength(InputStream s, int limit) {
|
||||
int length = read(s);
|
||||
checkArgument(length >= 0, "EOF found when length expected");
|
||||
checkArgument(length != 0x80, "indefinite-length encoding not supported");
|
||||
if (length > 127) {
|
||||
int size = length & 0x7f;
|
||||
checkArgument(size <= 4, "DER length more than 4 bytes: %s", size);
|
||||
length = 0;
|
||||
for (int i = 0; i < size; i++) {
|
||||
int next = read(s);
|
||||
checkArgument(next >= 0, "EOF found reading length");
|
||||
length = (length << 8) + next;
|
||||
}
|
||||
checkArgument(length >= 0, "corrupted stream - negative length %s found", length);
|
||||
checkArgument(length < limit, "corrupted stream - length %s out of bounds %s", length, limit);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
private static int read(InputStream s) {
|
||||
try {
|
||||
return s.read();
|
||||
} catch (IOException e) {
|
||||
throw propagate(e);// impossible as we are only using a byte array
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] toArray(InputStream limited) {
|
||||
try {
|
||||
return toByteArray(limited);
|
||||
} catch (IOException e) {
|
||||
throw propagate(e); // impossible as we are only using a byte array
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] encode(RSAPrivateCrtKey key) {
|
||||
List<BigInteger> seq = ImmutableList.<BigInteger> builder()
|
||||
.add(BigInteger.valueOf(0)) // version
|
||||
.add(key.getModulus())
|
||||
.add(key.getPublicExponent())
|
||||
.add(key.getPrivateExponent())
|
||||
.add(key.getPrimeP())
|
||||
.add(key.getPrimeQ())
|
||||
.add(key.getPrimeExponentP())
|
||||
.add(key.getPrimeExponentQ())
|
||||
.add(key.getCrtCoefficient()).build();
|
||||
int length = 0;
|
||||
for (BigInteger part : seq) {
|
||||
byte[] bytes = part.toByteArray();
|
||||
length += 1 + calculateBodyLength(bytes.length) + bytes.length;
|
||||
}
|
||||
|
||||
Builder<Byte> output = ImmutableList.<Byte> builder();
|
||||
output.add((byte) (SEQUENCE | CONSTRUCTED));
|
||||
writeLength(output, length);
|
||||
for (BigInteger part : seq) {
|
||||
byte[] bytes = part.toByteArray();
|
||||
output.add((byte) TAG);
|
||||
writeLength(output, bytes.length);
|
||||
output.addAll(Bytes.asList(bytes));
|
||||
}
|
||||
return Bytes.toArray(output.build());
|
||||
}
|
||||
|
||||
private static void writeLength(Builder<Byte> output, int length) {
|
||||
if (length > 127) {
|
||||
int size = 1;
|
||||
int val = length;
|
||||
while ((val >>>= 8) != 0) {
|
||||
size++;
|
||||
}
|
||||
output.add((byte) (size | 0x80));
|
||||
for (int i = (size - 1) * 8; i >= 0; i -= 8) {
|
||||
output.add((byte) (length >> i));
|
||||
}
|
||||
} else {
|
||||
output.add((byte) length);
|
||||
}
|
||||
}
|
||||
|
||||
private static int calculateBodyLength(int length) {
|
||||
int count = 1;
|
||||
if (length > 127) {
|
||||
int size = 1;
|
||||
int val = length;
|
||||
while ((val >>>= 8) != 0) {
|
||||
size++;
|
||||
}
|
||||
for (int i = (size - 1) * 8; i >= 0; i -= 8) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue