| /* |
| * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| import java.io.*; |
| import java.security.Key; |
| import java.security.KeyException; |
| import java.security.PublicKey; |
| import java.security.cert.*; |
| import java.util.*; |
| import javax.crypto.SecretKey; |
| import javax.xml.crypto.*; |
| import javax.xml.crypto.dsig.*; |
| import javax.xml.crypto.dom.*; |
| import javax.xml.crypto.dsig.keyinfo.*; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.DocumentBuilder; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.traversal.*; |
| import sun.security.util.DerValue; |
| import sun.security.x509.X500Name; |
| |
| /** |
| * This is a class which supplies several KeySelector implementations |
| */ |
| class KeySelectors { |
| |
| /** |
| * KeySelector which would always return the secret key specified in its |
| * constructor. |
| */ |
| static class SecretKeySelector extends KeySelector { |
| private SecretKey key; |
| SecretKeySelector(byte[] bytes) { |
| key = wrapBytes(bytes); |
| } |
| SecretKeySelector(SecretKey key) { |
| this.key = key; |
| } |
| |
| public KeySelectorResult select(KeyInfo ki, |
| KeySelector.Purpose purpose, |
| AlgorithmMethod method, |
| XMLCryptoContext context) |
| throws KeySelectorException { |
| return new SimpleKSResult(key); |
| } |
| |
| private SecretKey wrapBytes(final byte[] bytes) { |
| return new SecretKey() { |
| public String getFormat() { |
| return "RAW"; |
| } |
| |
| public String getAlgorithm() { |
| return "Secret key"; |
| } |
| |
| public byte[] getEncoded() { |
| return bytes.clone(); |
| } |
| }; |
| } |
| } |
| |
| /** |
| * KeySelector which would retrieve the X509Certificate out of the |
| * KeyInfo element and return the public key. |
| * NOTE: If there is an X509CRL in the KeyInfo element, then revoked |
| * certificate will be ignored. |
| */ |
| static class RawX509KeySelector extends KeySelector { |
| |
| public KeySelectorResult select(KeyInfo keyInfo, |
| KeySelector.Purpose purpose, |
| AlgorithmMethod method, |
| XMLCryptoContext context) |
| throws KeySelectorException { |
| if (keyInfo == null) { |
| throw new KeySelectorException("Null KeyInfo object!"); |
| } |
| // search for X509Data in keyinfo |
| for (XMLStructure kiType : keyInfo.getContent()) { |
| if (kiType instanceof X509Data) { |
| X509Data xd = (X509Data) kiType; |
| Object[] entries = xd.getContent().toArray(); |
| X509CRL crl = null; |
| // Looking for CRL before finding certificates |
| for (int i = 0; (i<entries.length&&crl != null); i++) { |
| if (entries[i] instanceof X509CRL) { |
| crl = (X509CRL) entries[i]; |
| } |
| } |
| boolean hasCRL = false; |
| for (Object o : xd.getContent()) { |
| // skip non-X509Certificate entries |
| if (o instanceof X509Certificate) { |
| if ((purpose != KeySelector.Purpose.VERIFY) && |
| (crl != null) && |
| crl.isRevoked((X509Certificate)o)) { |
| continue; |
| } else { |
| return new SimpleKSResult |
| (((X509Certificate)o).getPublicKey()); |
| } |
| } |
| } |
| } |
| } |
| throw new KeySelectorException("No X509Certificate found!"); |
| } |
| } |
| |
| /** |
| * KeySelector which would retrieve the public key out of the |
| * KeyValue element and return it. |
| * NOTE: If the key algorithm doesn't match signature algorithm, |
| * then the public key will be ignored. |
| */ |
| static class KeyValueKeySelector extends KeySelector { |
| public KeySelectorResult select(KeyInfo keyInfo, |
| KeySelector.Purpose purpose, |
| AlgorithmMethod method, |
| XMLCryptoContext context) |
| throws KeySelectorException { |
| if (keyInfo == null) { |
| throw new KeySelectorException("Null KeyInfo object!"); |
| } |
| SignatureMethod sm = (SignatureMethod) method; |
| |
| for (XMLStructure xmlStructure : keyInfo.getContent()) { |
| if (xmlStructure instanceof KeyValue) { |
| PublicKey pk = null; |
| try { |
| pk = ((KeyValue)xmlStructure).getPublicKey(); |
| } catch (KeyException ke) { |
| throw new KeySelectorException(ke); |
| } |
| // make sure algorithm is compatible with method |
| if (algEquals(sm.getAlgorithm(), pk.getAlgorithm())) { |
| return new SimpleKSResult(pk); |
| } |
| } |
| } |
| throw new KeySelectorException("No KeyValue element found!"); |
| } |
| |
| static boolean algEquals(String algURI, String algName) { |
| if (algName.equalsIgnoreCase("DSA") && |
| algURI.equals(SignatureMethod.DSA_SHA1) || |
| algURI.equals("http://www.w3.org/2009/xmldsig11#dsa-sha256")) { |
| return true; |
| } else if (algName.equalsIgnoreCase("RSA") && |
| (algURI.equals(SignatureMethod.RSA_SHA1) || |
| algURI.equals |
| ("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256") || |
| algURI.equals |
| ("http://www.w3.org/2001/04/xmldsig-more#rsa-sha384") || |
| algURI.equals |
| ("http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"))) { |
| return true; |
| } else if (algName.equalsIgnoreCase("EC") && |
| (algURI.equals |
| ("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1"))) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| } |
| |
| /** |
| * KeySelector which would perform special lookup as documented |
| * by the ie/baltimore/merlin-examples testcases and return the |
| * matching public key. |
| */ |
| static class CollectionKeySelector extends KeySelector { |
| private CertificateFactory cf; |
| private File certDir; |
| private Vector<X509Certificate> certs; |
| private static final int MATCH_SUBJECT = 0; |
| private static final int MATCH_ISSUER = 1; |
| private static final int MATCH_SERIAL = 2; |
| private static final int MATCH_SUBJECT_KEY_ID = 3; |
| private static final int MATCH_CERTIFICATE = 4; |
| |
| CollectionKeySelector(File dir) { |
| certDir = dir; |
| try { |
| cf = CertificateFactory.getInstance("X509"); |
| } catch (CertificateException ex) { |
| // not going to happen |
| } |
| certs = new Vector<X509Certificate>(); |
| File[] files = new File(certDir, "certs").listFiles(); |
| for (int i = 0; i < files.length; i++) { |
| try (FileInputStream fis = new FileInputStream(files[i])) { |
| certs.add((X509Certificate)cf.generateCertificate(fis)); |
| } catch (Exception ex) { } |
| } |
| } |
| |
| Vector<X509Certificate> match(int matchType, Object value, |
| Vector<X509Certificate> pool) { |
| Vector<X509Certificate> matchResult = new Vector<>(); |
| for (int j=0; j < pool.size(); j++) { |
| X509Certificate c = pool.get(j); |
| switch (matchType) { |
| case MATCH_SUBJECT: |
| try { |
| if (c.getSubjectDN().equals(new X500Name((String)value))) { |
| matchResult.add(c); |
| } |
| } catch (IOException ioe) { } |
| break; |
| case MATCH_ISSUER: |
| try { |
| if (c.getIssuerDN().equals(new X500Name((String)value))) { |
| matchResult.add(c); |
| } |
| } catch (IOException ioe) { } |
| break; |
| case MATCH_SERIAL: |
| if (c.getSerialNumber().equals(value)) { |
| matchResult.add(c); |
| } |
| |
| break; |
| case MATCH_SUBJECT_KEY_ID: |
| byte[] extension = c.getExtensionValue("2.5.29.14"); |
| if (extension != null) { |
| try { |
| DerValue derValue = new DerValue(extension); |
| DerValue derValue2 = new DerValue(derValue.getOctetString()); |
| byte[] extVal = derValue2.getOctetString(); |
| |
| if (Arrays.equals(extVal, (byte[]) value)) { |
| matchResult.add(c); |
| } |
| } catch (IOException ex) { } |
| } |
| break; |
| case MATCH_CERTIFICATE: |
| if (c.equals(value)) { |
| matchResult.add(c); |
| } |
| break; |
| } |
| } |
| return matchResult; |
| } |
| |
| public KeySelectorResult select(KeyInfo keyInfo, |
| KeySelector.Purpose purpose, |
| AlgorithmMethod method, |
| XMLCryptoContext context) |
| throws KeySelectorException { |
| if (keyInfo == null) { |
| throw new KeySelectorException("Null KeyInfo object!"); |
| } |
| for (XMLStructure xmlStructure : keyInfo.getContent()) { |
| try { |
| if (xmlStructure instanceof KeyName) { |
| String name = ((KeyName)xmlStructure).getName(); |
| PublicKey pk = null; |
| File certFile = new File(new File(certDir, "certs"), |
| name.toLowerCase() + ".crt"); |
| try (FileInputStream fis = new FileInputStream(certFile)) { |
| // Lookup the public key using the key name 'Xxx', |
| // i.e. the public key is in "certs/xxx.crt". |
| X509Certificate cert = (X509Certificate) |
| cf.generateCertificate(fis); |
| pk = cert.getPublicKey(); |
| } catch (FileNotFoundException e) { |
| // assume KeyName contains subject DN and search |
| // collection of certs for match |
| Vector<X509Certificate> result = |
| match(MATCH_SUBJECT, name, certs); |
| int numOfMatches = (result==null? 0:result.size()); |
| if (numOfMatches != 1) { |
| throw new KeySelectorException |
| ((numOfMatches==0?"No":"More than one") + |
| " match found"); |
| } |
| pk = result.get(0).getPublicKey(); |
| } |
| return new SimpleKSResult(pk); |
| } else if (xmlStructure instanceof RetrievalMethod) { |
| // Lookup the public key using the retrievel method. |
| // NOTE: only X509Certificate type is supported. |
| RetrievalMethod rm = (RetrievalMethod) xmlStructure; |
| String type = rm.getType(); |
| if (type.equals(X509Data.RAW_X509_CERTIFICATE_TYPE)) { |
| String uri = rm.getURI(); |
| try (FileInputStream fis = |
| new FileInputStream(new File(certDir, uri))) { |
| X509Certificate cert = (X509Certificate) |
| cf.generateCertificate(fis); |
| return new SimpleKSResult(cert.getPublicKey()); |
| } |
| } else { |
| throw new KeySelectorException |
| ("Unsupported RetrievalMethod type"); |
| } |
| } else if (xmlStructure instanceof X509Data) { |
| List content = ((X509Data)xmlStructure).getContent(); |
| Vector<X509Certificate> result = null; |
| // Lookup the public key using the information |
| // specified in X509Data element, i.e. searching |
| // over the collection of certificate files under |
| // "certs" subdirectory and return those match. |
| for (Object obj : content) { |
| if (obj instanceof String) { |
| result = match(MATCH_SUBJECT, obj, certs); |
| } else if (obj instanceof byte[]) { |
| result = match(MATCH_SUBJECT_KEY_ID, obj, |
| certs); |
| } else if (obj instanceof X509Certificate) { |
| result = match(MATCH_CERTIFICATE, obj, certs); |
| } else if (obj instanceof X509IssuerSerial) { |
| X509IssuerSerial is = (X509IssuerSerial) obj; |
| result = match(MATCH_SERIAL, |
| is.getSerialNumber(), certs); |
| result = match(MATCH_ISSUER, |
| is.getIssuerName(), result); |
| } else { |
| throw new KeySelectorException("Unsupported X509Data: " + obj); |
| } |
| } |
| int numOfMatches = (result==null? 0:result.size()); |
| if (numOfMatches != 1) { |
| throw new KeySelectorException |
| ((numOfMatches==0?"No":"More than one") + |
| " match found"); |
| } |
| return new SimpleKSResult(result.get(0).getPublicKey()); |
| } |
| } catch (Exception ex) { |
| throw new KeySelectorException(ex); |
| } |
| } |
| throw new KeySelectorException("No matching key found!"); |
| } |
| } |
| |
| static class ByteUtil { |
| |
| private static String mapping = "0123456789ABCDEF"; |
| private static int numBytesPerRow = 6; |
| |
| private static String getHex(byte value) { |
| int low = value & 0x0f; |
| int high = ((value >> 4) & 0x0f); |
| char[] res = new char[2]; |
| res[0] = mapping.charAt(high); |
| res[1] = mapping.charAt(low); |
| return new String(res); |
| } |
| |
| static String dumpArray(byte[] in) { |
| int numDumped = 0; |
| StringBuffer buf = new StringBuffer(512); |
| buf.append("{"); |
| for (int i=0;i<(in.length/numBytesPerRow); i++) { |
| for (int j=0; j<(numBytesPerRow); j++) { |
| buf.append("(byte)0x" + getHex(in[i*numBytesPerRow+j]) + |
| ", "); |
| } |
| numDumped += numBytesPerRow; |
| } |
| while (numDumped < in.length) { |
| buf.append("(byte)0x" + getHex(in[numDumped]) + " "); |
| numDumped += 1; |
| } |
| buf.append("}"); |
| return buf.toString(); |
| } |
| } |
| } |
| |
| class SimpleKSResult implements KeySelectorResult { |
| private final Key key; |
| |
| SimpleKSResult(Key key) { this.key = key; } |
| |
| public Key getKey() { return key; } |
| } |