/*
* Copyright (c) 2011-2012 ICM Uniwersytet Warszawski All rights reserved.
* See LICENCE file for licensing information.
*
* Derived from the code copyrighted and licensed as follows:
*
* Copyright (c) Members of the EGEE Collaboration. 2004.
* See http://www.eu-egee.org/partners/ for details on the copyright
* holders.
*
* Licensed 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.
*/
package eu.emi.security.authn.x509.helpers.proxy;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralSubtree;
import eu.emi.security.authn.x509.helpers.CertificateHelpers;
/**
* An utility class for defining the allowed address space, used both to define
* the source and target restrictions. The format is:
*
* <pre>
* iGTFProxyRestrictFrom ::= NameConstraints
* iGTFProxyRestrictTarget ::= NameConstraints
*
* NameConstraints::= SEQUENCE {
* permittedSubtrees [0] GeneralSubtrees OPTIONAL,
* excludedSubtrees [1] GeneralSubtrees OPTIONAL }
*
* GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
*
* GeneralSubtree ::= SEQUENCE {
* base GeneralName,
* minimum [0] BaseDistance DEFAULT 0,
* maximum [1] BaseDistance OPTIONAL }
*
* BaseDistance ::= INTEGER (0..MAX)
*
* GeneralName ::= CHOICE {
* otherName [0] OtherName,
* rfc822Name [1] IA5String,
* dNSName [2] IA5String,
* x400Address [3] ORAddress,
* directoryName [4] Name,
* ediPartyName [5] EDIPartyName,
* uniformResourceIdentifier [6] IA5String,
* iPAddress [7] OCTET STRING,
* registeredID [8] OBJECT IDENTIFIER }
*
* OtherName ::= SEQUENCE {
* type-id OBJECT IDENTIFIER,
* value [0] EXPLICIT ANY DEFINED BY type-id }
*
* EDIPartyName ::= SEQUENCE {
* nameAssigner [0] DirectoryString OPTIONAL,
* partyName [1] DirectoryString }
* </pre>
*
* And in this class only the IPAddress as a IP address - netmask combination is
* supported.
*
* @author joni.hahkala@cern.ch
* @author K. Benedyczak
*/
public class ProxyAddressRestrictionData extends ASN1Object
{
public static final String SOURCE_RESTRICTION_OID = "1.2.840.113612.5.5.1.1.2.1";
public static final String TARGET_RESTRICTION_OID = "1.2.840.113612.5.5.1.1.2.2";
private List<GeneralSubtree> permittedGeneralSubtrees = new ArrayList<GeneralSubtree>();
private List<GeneralSubtree> excludedGeneralSubtrees = new ArrayList<GeneralSubtree>();
/**
* Parses the restriction data from byte array.
*
* @param bytes The byte array to parse.
* @throws IOException In case there is a problem parsing the structure.
*/
public ProxyAddressRestrictionData(byte[] bytes) throws IOException
{
ASN1Sequence nameSpaceRestrictionsSeq = (ASN1Sequence) ASN1Primitive.fromByteArray(bytes);
switch (nameSpaceRestrictionsSeq.size())
{
case 0:
return;
case 1:
DERTaggedObject taggedSequence = (DERTaggedObject) nameSpaceRestrictionsSeq.getObjectAt(0);
if (taggedSequence.getTagNo() == 0)
{
copyCondSequenceToVector((DERSequence) taggedSequence.getObject(),
permittedGeneralSubtrees);
} else
{
if (taggedSequence.getTagNo() == 1)
{
copyCondSequenceToVector((DERSequence) taggedSequence.getObject(),
excludedGeneralSubtrees);
} else
{
throw new IllegalArgumentException("Illegal tag number in the proxy restriction NameConstraints data structure: "
+ taggedSequence.getTagNo() + ", should have been 0 or 1");
}
}
break;
case 2:
taggedSequence = (DERTaggedObject) nameSpaceRestrictionsSeq.getObjectAt(0);
if (taggedSequence.getTagNo() == 0)
{
copyCondSequenceToVector((DERSequence) taggedSequence.getObject(),
permittedGeneralSubtrees);
} else
{
throw new IllegalArgumentException(
"Illegal tag number in the proxy restriction NameConstraints data structure at the first position: "
+ taggedSequence.getTagNo() + ", should have been 0");
}
taggedSequence = (DERTaggedObject) nameSpaceRestrictionsSeq.getObjectAt(1);
if (taggedSequence.getTagNo() == 1)
{
copyCondSequenceToVector((DERSequence) taggedSequence.getObject(),
excludedGeneralSubtrees);
} else
{
throw new IllegalArgumentException(
"Illegal tag number in the proxy restriction NameConstraints data structure at the second position: "
+ taggedSequence.getTagNo() + ", should have been 1");
}
break;
default:
throw new IllegalArgumentException(
"Illegal number of items in the proxy restriction NameConstraints data structure: "
+ nameSpaceRestrictionsSeq.size() + ", should have been 0 to 2");
}
}
/**
* Creates an instance of the extension of the given type from a certificate.
* @param certificate certificate
* @param source whether to create object representing the source restriction (if true) or target (if value is false).
* @return null if the certificate does not have the required extension, initialized object otherwise.
* @throws IOException IO exception
*/
public static ProxyAddressRestrictionData getInstance(X509Certificate certificate, boolean source)
throws IOException
{
byte []ext = CertificateHelpers.getExtensionBytes(certificate,
source ? SOURCE_RESTRICTION_OID : TARGET_RESTRICTION_OID);
if (ext == null)
return null;
return new ProxyAddressRestrictionData(ext);
}
/**
* Constructor to generate an empty ProxyRestrictionData object for
* creating new restrictions. Notice that putting an empty proxy
* restriction into a certificate means that there are no permitted IP
* spaces, meaning the proxy should be rejected everywhere.
*/
public ProxyAddressRestrictionData()
{
// creates empty restriction data object.
}
/**
* This method copies the contents of a generalSubtrees sequence into
* the given vector. Static to protect the internal data structures from
* access.
*
* @param subSeq
* the subsequence to copy.
* @param vector
* The target to copy the parsed GeneralSubtree objects.
*/
private static void copyCondSequenceToVector(DERSequence subSeq,
List<GeneralSubtree> vector)
{
Enumeration<?> subTreeEnum = subSeq.getObjects();
while (subTreeEnum.hasMoreElements())
{
ASN1Primitive object = (ASN1Primitive) subTreeEnum.nextElement();
vector.add(GeneralSubtree.getInstance(object));
}
}
/**
* Adds a new permitted IP addressSpace to the data structure.
*
* @param address The address space to add to the allowed ip address
* space. Example of the format: 192.168.0.0/16. Which
* equals a 192.168.0.0 with a net mask 255.255.0.0. A
* single IP address can be defined as
* xxx.xxx.xxx.xxx/32. <br> It is also possible to provide IPv6
* addresses.
* See <a href="http://www.ietf.org/rfc/rfc4632.txt"> RFC4632.</a>
*/
public void addPermittedIPAddressWithNetmask(String address)
{
permittedGeneralSubtrees.add(new GeneralSubtree(new GeneralName(
GeneralName.iPAddress, address), null, null));
}
/**
* Adds a new excluded IP addressSpace to the data structure.
*
* @param address The address space to add to the allowed ip address
* space. Example of the format: 192.168.0.0/16. Which
* equals a 192.168.0.0 with a net mask 255.255.0.0. A
* single IP address can be defined as
* xxx.xxx.xxx.xxx/32. <br> It is also possible to provide IPv6
* addresses. See <a href="http://www.ietf.org/rfc/rfc4632.txt"> RFC4632.</a>
*/
public void addExcludedIPAddressWithNetmask(String address)
{
excludedGeneralSubtrees.add(new GeneralSubtree(new GeneralName(
GeneralName.iPAddress, address), null, null));
}
/**
* Returns the NameConstraints structure of the restrictions.
*
* @return The DERSequence containing the NameConstraints structure.
*/
@Override
public ASN1Primitive toASN1Primitive()
{
ASN1EncodableVector nameConstraintsSequenceVector = new ASN1EncodableVector();
addTaggedSequenceOfSubtrees(0, permittedGeneralSubtrees,
nameConstraintsSequenceVector);
addTaggedSequenceOfSubtrees(1, excludedGeneralSubtrees,
nameConstraintsSequenceVector);
return new DERSequence(nameConstraintsSequenceVector);
}
/**
* Adds, with the given tag, a DER sequence object that contains the
* GeneralSubtree objects into the ASN1Vector.
*
* @param tagNo
* The tag to tag the object.
* @param subtrees
* The Vector of GeneralSubtree objects. Null will throw
* NullPointerException. An empty Vector will not be
* added.
* @param asn1Vector
* The vector to add the subtrees sequence with the given
* tag.
*/
private static void addTaggedSequenceOfSubtrees(int tagNo, List<GeneralSubtree> subtrees,
ASN1EncodableVector asn1Vector)
{
if (!subtrees.isEmpty())
{
ASN1EncodableVector subtreesSequenceVector = new ASN1EncodableVector();
Iterator<GeneralSubtree> generalSubtreesEnum = subtrees.iterator();
while (generalSubtreesEnum.hasNext())
{
subtreesSequenceVector.add(generalSubtreesEnum.next());
}
asn1Vector.add(new DERTaggedObject(tagNo, new DERSequence(
subtreesSequenceVector)));
}
}
/**
* Returns a Vector of Vectors of IP address spaces as defined in rfc
* 4632.
*
* @see #addExcludedIPAddressWithNetmask(String)
* @return The array of arrays of string representation of address
* spaces defined in this structure. The first element in the
* array lists the permitted IP address spaces and the second
* the excluded IP spaces. In format ipaddress/netmask bytes.
* Example {137,138,0,0,255,255,0,0}. Array always contains two
* items, but they can be of length 0.
*/
public byte[][][] getIPSpaces()
{
byte allowedIPSpaces[][] = subtreesIntoArray(permittedGeneralSubtrees);
byte excludedIPSpaces[][] = subtreesIntoArray(excludedGeneralSubtrees);
return new byte[][][] { allowedIPSpaces, excludedIPSpaces };
}
public String[] getPermittedAddresses()
{
byte[][][] spaces = getIPSpaces();
return convert2strings(spaces[0]);
}
public String[] getExcludedAddresses()
{
byte[][][] spaces = getIPSpaces();
return convert2strings(spaces[1]);
}
/**
* Generates a string array of IP address spaces from a list of
* GeneralSubtrees.
*
* @param subtrees The list of GeneralSubtrees to parse. Null as input
* will return null.
* @return the array of IP address spaces.
*/
private static byte[][] subtreesIntoArray(List<GeneralSubtree> subtrees)
{
if (subtrees == null)
return null;
List<byte[]> ips = new ArrayList<byte[]>();
Iterator<GeneralSubtree> enumGeneralNames = subtrees.iterator();
while (enumGeneralNames.hasNext())
{
GeneralName item = enumGeneralNames.next().getBase();
if (item.getTagNo() == GeneralName.iPAddress)
{
ASN1OctetString octets = (ASN1OctetString) item.getName();
byte[] bytes = octets.getOctets();
ips.add(bytes);
}
}
return ips.toArray(new byte[ips.size()][]);
}
public static String convert2sr(byte[] src)
{
int half = src.length/2;
StringBuilder ret = new StringBuilder(40);
boolean ipv6 = src.length == 32;
for (int i=0; i<half; i++)
{
ret.append(ipv6 ? Integer.toHexString(src[i]&255) : src[i]&255);
if (i<half-1)
ret.append(ipv6? ":" : ".");
}
ret.append("/");
int mask = 0;
for (int i=half; i<src.length; i++)
mask += Integer.bitCount(src[i]&255);
ret.append(mask);
return ret.toString();
}
public static String[] convert2strings(byte[][] src)
{
String[] ret = new String[src.length];
for (int i=0; i<src.length; i++)
ret[i] = convert2sr(src[i]);
return ret;
}
}