173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root/* 273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. 373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * 573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * This code is free software; you can redistribute it and/or modify it 673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * under the terms of the GNU General Public License version 2 only, as 773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * published by the Free Software Foundation. Oracle designates this 873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * particular file as subject to the "Classpath" exception as provided 973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * by Oracle in the LICENSE file that accompanied this code. 1073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * 1173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * This code is distributed in the hope that it will be useful, but WITHOUT 1273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * version 2 for more details (a copy is included in the LICENSE file that 1573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * accompanied this code). 1673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * 1773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * You should have received a copy of the GNU General Public License version 1873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * 2 along with this work; if not, write to the Free Software Foundation, 1973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 2073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * 2173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 2273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * or visit www.oracle.com if you need additional information or have any 2373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * questions. 2473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root */ 2573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 2673405ff8729cca39da90b2e2f604062e323f6f7aKenny Rootpackage sun.security.provider.certpath; 2773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 2873405ff8729cca39da90b2e2f604062e323f6f7aKenny Rootimport java.io.IOException; 2973405ff8729cca39da90b2e2f604062e323f6f7aKenny Rootimport java.math.BigInteger; 3073405ff8729cca39da90b2e2f604062e323f6f7aKenny Rootimport java.security.MessageDigest; 3173405ff8729cca39da90b2e2f604062e323f6f7aKenny Rootimport java.security.NoSuchAlgorithmException; 3273405ff8729cca39da90b2e2f604062e323f6f7aKenny Rootimport java.security.PublicKey; 3373405ff8729cca39da90b2e2f604062e323f6f7aKenny Rootimport java.security.cert.X509Certificate; 3473405ff8729cca39da90b2e2f604062e323f6f7aKenny Rootimport java.util.Arrays; 3573405ff8729cca39da90b2e2f604062e323f6f7aKenny Rootimport javax.security.auth.x500.X500Principal; 3673405ff8729cca39da90b2e2f604062e323f6f7aKenny Rootimport sun.misc.HexDumpEncoder; 3773405ff8729cca39da90b2e2f604062e323f6f7aKenny Rootimport sun.security.x509.*; 3873405ff8729cca39da90b2e2f604062e323f6f7aKenny Rootimport sun.security.util.*; 3973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 4073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root/** 4173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * This class corresponds to the CertId field in OCSP Request 4273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * and the OCSP Response. The ASN.1 definition for CertID is defined 4373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * in RFC 2560 as: 4473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * <pre> 4573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * 4673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * CertID ::= SEQUENCE { 4773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * hashAlgorithm AlgorithmIdentifier, 4873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * issuerNameHash OCTET STRING, -- Hash of Issuer's DN 4973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * issuerKeyHash OCTET STRING, -- Hash of Issuers public key 5073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * serialNumber CertificateSerialNumber 5173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * } 5273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * 5373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * </pre> 5473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * 5573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * @author Ram Marti 5673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root */ 5773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 5873405ff8729cca39da90b2e2f604062e323f6f7aKenny Rootpublic class CertId { 5973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 6073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root private static final boolean debug = false; 6173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root private static final AlgorithmId SHA1_ALGID 6273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root = new AlgorithmId(AlgorithmId.SHA_oid); 6373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root private final AlgorithmId hashAlgId; 6473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root private final byte[] issuerNameHash; 6573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root private final byte[] issuerKeyHash; 6673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root private final SerialNumber certSerialNumber; 6773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root private int myhash = -1; // hashcode for this CertId 6873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 6973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root /** 7073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * Creates a CertId. The hash algorithm used is SHA-1. 7173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root */ 7273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root public CertId(X509Certificate issuerCert, SerialNumber serialNumber) 7373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root throws IOException { 7473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 7573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root this(issuerCert.getSubjectX500Principal(), 7673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root issuerCert.getPublicKey(), serialNumber); 7773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 7873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 7973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root public CertId(X500Principal issuerName, PublicKey issuerKey, 8073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root SerialNumber serialNumber) throws IOException { 8173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 8273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root // compute issuerNameHash 8373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root MessageDigest md = null; 8473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root try { 8573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root md = MessageDigest.getInstance("SHA1"); 8673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } catch (NoSuchAlgorithmException nsae) { 8773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root throw new IOException("Unable to create CertId", nsae); 8873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 8973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root hashAlgId = SHA1_ALGID; 9073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root md.update(issuerName.getEncoded()); 9173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root issuerNameHash = md.digest(); 9273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 9373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root // compute issuerKeyHash (remove the tag and length) 9473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root byte[] pubKey = issuerKey.getEncoded(); 9573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root DerValue val = new DerValue(pubKey); 9673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root DerValue[] seq = new DerValue[2]; 9773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root seq[0] = val.data.getDerValue(); // AlgorithmID 9873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root seq[1] = val.data.getDerValue(); // Key 9973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root byte[] keyBytes = seq[1].getBitString(); 10073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root md.update(keyBytes); 10173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root issuerKeyHash = md.digest(); 10273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root certSerialNumber = serialNumber; 10373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 10473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root if (debug) { 10573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root HexDumpEncoder encoder = new HexDumpEncoder(); 10673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root System.out.println("Issuer Name is " + issuerName); 10773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root System.out.println("issuerNameHash is " + 10873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root encoder.encodeBuffer(issuerNameHash)); 10973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root System.out.println("issuerKeyHash is " + 11073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root encoder.encodeBuffer(issuerKeyHash)); 11173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root System.out.println("SerialNumber is " + serialNumber.getNumber()); 11273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 11373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 11473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 11573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root /** 11673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * Creates a CertId from its ASN.1 DER encoding. 11773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root */ 11873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root public CertId(DerInputStream derIn) throws IOException { 11973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root hashAlgId = AlgorithmId.parse(derIn.getDerValue()); 12073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root issuerNameHash = derIn.getOctetString(); 12173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root issuerKeyHash = derIn.getOctetString(); 12273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root certSerialNumber = new SerialNumber(derIn); 12373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 12473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 12573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root /** 12673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * Return the hash algorithm identifier. 12773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root */ 12873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root public AlgorithmId getHashAlgorithm() { 12973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root return hashAlgId; 13073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 13173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 13273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root /** 13373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * Return the hash value for the issuer name. 13473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root */ 13573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root public byte[] getIssuerNameHash() { 13673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root return issuerNameHash; 13773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 13873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 13973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root /** 14073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * Return the hash value for the issuer key. 14173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root */ 14273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root public byte[] getIssuerKeyHash() { 14373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root return issuerKeyHash; 14473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 14573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 14673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root /** 14773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * Return the serial number. 14873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root */ 14973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root public BigInteger getSerialNumber() { 15073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root return certSerialNumber.getNumber(); 15173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 15273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 15373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root /** 15473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * Encode the CertId using ASN.1 DER. 15573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * The hash algorithm used is SHA-1. 15673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root */ 15773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root public void encode(DerOutputStream out) throws IOException { 15873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 15973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root DerOutputStream tmp = new DerOutputStream(); 16073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root hashAlgId.encode(tmp); 16173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root tmp.putOctetString(issuerNameHash); 16273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root tmp.putOctetString(issuerKeyHash); 16373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root certSerialNumber.encode(tmp); 16473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root out.write(DerValue.tag_Sequence, tmp); 16573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 16673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root if (debug) { 16773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root HexDumpEncoder encoder = new HexDumpEncoder(); 16873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root System.out.println("Encoded certId is " + 16973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root encoder.encode(out.toByteArray())); 17073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 17173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 17273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 17373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root /** 17473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * Returns a hashcode value for this CertId. 17573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * 17673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * @return the hashcode value. 17773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root */ 17873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root @Override public int hashCode() { 17973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root if (myhash == -1) { 18073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root myhash = hashAlgId.hashCode(); 18173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root for (int i = 0; i < issuerNameHash.length; i++) { 18273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root myhash += issuerNameHash[i] * i; 18373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 18473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root for (int i = 0; i < issuerKeyHash.length; i++) { 18573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root myhash += issuerKeyHash[i] * i; 18673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 18773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root myhash += certSerialNumber.getNumber().hashCode(); 18873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 18973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root return myhash; 19073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 19173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 19273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root /** 19373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * Compares this CertId for equality with the specified 19473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * object. Two CertId objects are considered equal if their hash algorithms, 19573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * their issuer name and issuer key hash values and their serial numbers 19673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * are equal. 19773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * 19873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * @param other the object to test for equality with this object. 19973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * @return true if the objects are considered equal, false otherwise. 20073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root */ 20173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root @Override public boolean equals(Object other) { 20273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root if (this == other) { 20373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root return true; 20473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 20573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root if (other == null || (!(other instanceof CertId))) { 20673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root return false; 20773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 20873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 20973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root CertId that = (CertId) other; 21073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root if (hashAlgId.equals(that.getHashAlgorithm()) && 21173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root Arrays.equals(issuerNameHash, that.getIssuerNameHash()) && 21273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root Arrays.equals(issuerKeyHash, that.getIssuerKeyHash()) && 21373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root certSerialNumber.getNumber().equals(that.getSerialNumber())) { 21473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root return true; 21573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } else { 21673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root return false; 21773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 21873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 21973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root 22073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root /** 22173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root * Create a string representation of the CertId. 22273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root */ 22373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root @Override public String toString() { 22473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root StringBuilder sb = new StringBuilder(); 22573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root sb.append("CertId \n"); 22673405ff8729cca39da90b2e2f604062e323f6f7aKenny Root sb.append("Algorithm: " + hashAlgId.toString() +"\n"); 22773405ff8729cca39da90b2e2f604062e323f6f7aKenny Root sb.append("issuerNameHash \n"); 22873405ff8729cca39da90b2e2f604062e323f6f7aKenny Root HexDumpEncoder encoder = new HexDumpEncoder(); 22973405ff8729cca39da90b2e2f604062e323f6f7aKenny Root sb.append(encoder.encode(issuerNameHash)); 23073405ff8729cca39da90b2e2f604062e323f6f7aKenny Root sb.append("\nissuerKeyHash: \n"); 23173405ff8729cca39da90b2e2f604062e323f6f7aKenny Root sb.append(encoder.encode(issuerKeyHash)); 23273405ff8729cca39da90b2e2f604062e323f6f7aKenny Root sb.append("\n" + certSerialNumber.toString()); 23373405ff8729cca39da90b2e2f604062e323f6f7aKenny Root return sb.toString(); 23473405ff8729cca39da90b2e2f604062e323f6f7aKenny Root } 23573405ff8729cca39da90b2e2f604062e323f6f7aKenny Root} 236