diff --git a/pom.xml b/pom.xml index 6836526..a7948ff 100644 --- a/pom.xml +++ b/pom.xml @@ -64,20 +64,20 @@ org.slf4j slf4j-api - 1.7.32 + 1.7.36 org.slf4j slf4j-log4j12 - 1.7.32 + 1.7.36 test org.bouncycastle - bcprov-jdk15on - 1.69 + bcprov-jdk18on + 1.76 diff --git a/src/main/java/jcifs/pac/ASN1Util.java b/src/main/java/jcifs/pac/ASN1Util.java index a015ba5..239bd0d 100644 --- a/src/main/java/jcifs/pac/ASN1Util.java +++ b/src/main/java/jcifs/pac/ASN1Util.java @@ -17,13 +17,12 @@ package jcifs.pac; +import java.io.EOFException; import java.io.IOException; +import java.io.InputStream; import java.util.Enumeration; -import org.bouncycastle.asn1.ASN1InputStream; -import org.bouncycastle.asn1.ASN1Primitive; -import org.bouncycastle.asn1.ASN1TaggedObject; -import org.bouncycastle.asn1.DLSequence; +import org.bouncycastle.asn1.*; /** @@ -77,16 +76,27 @@ public static T as ( Class type, ASN1InputStream st /** - * + * * @param type * @param tagged * @return tagged object contents cast to type * @throws PACDecodingException */ public static T as ( Class type, ASN1TaggedObject tagged ) throws PACDecodingException { - return as(type, tagged.getObject()); + return as(type, tagged.getBaseObject()); } + /** + * + * @param type + * @param sequence + * @param index + * @return sequence element cast to type + * @throws PACDecodingException + */ + public static T as ( Class type, DLSequence sequence, int index ) throws PACDecodingException { + return as(type, sequence, index); + } /** * @@ -96,8 +106,134 @@ public static T as ( Class type, ASN1TaggedObject t * @return sequence element cast to type * @throws PACDecodingException */ - public static T as ( Class type, DLSequence sequence, int index ) throws PACDecodingException { + public static T as ( Class type, ASN1Sequence sequence, int index ) throws PACDecodingException { return as(type, sequence.getObjectAt(index)); } + + /** + * Read a tagged object without parsing it's contents + * + * BC no longer seems to allow that out of the box + * + * @param expectTag + * @param in + * @return coded bytes of the tagged object + * @throws IOException + */ + public static byte[] readUnparsedTagged(int expectTag, int limit, ASN1InputStream in) throws IOException { + int ftag = in.read(); + int tag = readTagNumber(in, ftag); + if ( tag != expectTag ) { + throw new IOException("Unexpected tag " + tag); + } + int length = readLength(in, limit, false); + byte[] content = new byte[length]; + in.read(content); + return content; + } + + // shamelessly stolen from BC ASN1InputStream + static int readTagNumber(InputStream s, int tag) + throws IOException + { + int tagNo = tag & 0x1f; + + // + // with tagged object tag number is bottom 5 bits, or stored at the start of the content + // + if (tagNo == 0x1f) + { + int b = s.read(); + if (b < 31) + { + if (b < 0) + { + throw new EOFException("EOF found inside tag value."); + } + throw new IOException("corrupted stream - high tag number < 31 found"); + } + + tagNo = b & 0x7f; + + // X.690-0207 8.1.2.4.2 + // "c) bits 7 to 1 of the first subsequent octet shall not all be zero." + if (0 == tagNo) + { + throw new IOException("corrupted stream - invalid high tag number found"); + } + + while ((b & 0x80) != 0) + { + if ((tagNo >>> 24) != 0) + { + throw new IOException("Tag number more than 31 bits"); + } + + tagNo <<= 7; + + b = s.read(); + if (b < 0) + { + throw new EOFException("EOF found inside tag value."); + } + + tagNo |= (b & 0x7f); + } + } + + return tagNo; + } + + static int readLength(InputStream s, int limit, boolean isParsing) + throws IOException + { + int length = s.read(); + if (0 == (length >>> 7)) + { + // definite-length short form + return length; + } + if (0x80 == length) + { + // indefinite-length + return -1; + } + if (length < 0) + { + throw new EOFException("EOF found when length expected"); + } + if (0xFF == length) + { + throw new IOException("invalid long form definite-length 0xFF"); + } + + int octetsCount = length & 0x7F, octetsPos = 0; + + length = 0; + do + { + int octet = s.read(); + if (octet < 0) + { + throw new EOFException("EOF found reading length"); + } + + if ((length >>> 23) != 0) + { + throw new IOException("long form definite-length more than 31 bits"); + } + + length = (length << 8) + octet; + } + while (++octetsPos < octetsCount); + + if (length >= limit && !isParsing) // after all we must have read at least 1 byte + { + throw new IOException("corrupted stream - out of bounds length found: " + length + " >= " + limit); + } + + return length; + } + } diff --git a/src/main/java/jcifs/pac/kerberos/KerberosApRequest.java b/src/main/java/jcifs/pac/kerberos/KerberosApRequest.java index a997d7e..5482a21 100644 --- a/src/main/java/jcifs/pac/kerberos/KerberosApRequest.java +++ b/src/main/java/jcifs/pac/kerberos/KerberosApRequest.java @@ -24,12 +24,7 @@ import javax.security.auth.kerberos.KerberosKey; -import org.bouncycastle.asn1.ASN1InputStream; -import org.bouncycastle.asn1.ASN1Integer; -import org.bouncycastle.asn1.ASN1TaggedObject; -import org.bouncycastle.asn1.DERApplicationSpecific; -import org.bouncycastle.asn1.DERBitString; -import org.bouncycastle.asn1.DLSequence; +import org.bouncycastle.asn1.*; import jcifs.pac.ASN1Util; import jcifs.pac.PACDecodingException; @@ -43,20 +38,27 @@ public class KerberosApRequest { public KerberosApRequest ( byte[] token, KerberosKey[] keys ) throws PACDecodingException { + this(parseSequence(token), keys); + } + + private static ASN1Sequence parseSequence(byte[] token) throws PACDecodingException { if ( token.length <= 0 ) throw new PACDecodingException("Empty kerberos ApReq"); - DLSequence sequence; + ASN1Sequence sequence; try { try ( ASN1InputStream stream = new ASN1InputStream(new ByteArrayInputStream(token)) ) { - sequence = ASN1Util.as(DLSequence.class, stream); + sequence = ASN1Util.as(ASN1Sequence.class, stream); } } catch ( IOException e ) { throw new PACDecodingException("Malformed Kerberos Ticket", e); } + return sequence; + } - Enumeration fields = sequence.getObjects(); + public KerberosApRequest(ASN1Sequence seq, KerberosKey[] keys) throws PACDecodingException { + Enumeration fields = seq.getObjects(); while ( fields.hasMoreElements() ) { ASN1TaggedObject tagged = ASN1Util.as(ASN1TaggedObject.class, fields.nextElement()); switch ( tagged.getTagNo() ) { @@ -76,10 +78,14 @@ public KerberosApRequest ( byte[] token, KerberosKey[] keys ) throws PACDecoding this.apOptions = bitString.getBytes()[ 0 ]; break; case 3: - DERApplicationSpecific derTicket = ASN1Util.as(DERApplicationSpecific.class, tagged); - if ( !derTicket.isConstructed() ) + ASN1TaggedObject derTicket = ASN1Util.as(ASN1TaggedObject.class, tagged); + if ( derTicket.getTagClass() != BERTags.APPLICATION ) throw new PACDecodingException("Malformed Kerberos Ticket"); - this.ticket = new KerberosTicket(derTicket.getContents(), this.apOptions, keys); + try { + this.ticket = new KerberosTicket(derTicket.getBaseObject().getEncoded(), this.apOptions, keys); + } catch (IOException e) { + throw new PACDecodingException("Malformed Kerberos Ticket", e); + } break; case 4: // Let's ignore this for now diff --git a/src/main/java/jcifs/pac/kerberos/KerberosCredentials.java b/src/main/java/jcifs/pac/kerberos/KerberosCredentials.java index ec22228..5181b8d 100644 --- a/src/main/java/jcifs/pac/kerberos/KerberosCredentials.java +++ b/src/main/java/jcifs/pac/kerberos/KerberosCredentials.java @@ -24,6 +24,7 @@ import javax.security.auth.Subject; import javax.security.auth.kerberos.KerberosKey; +import javax.security.auth.kerberos.KeyTab; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; diff --git a/src/main/java/jcifs/pac/kerberos/KerberosEncData.java b/src/main/java/jcifs/pac/kerberos/KerberosEncData.java index 38bad7f..396c1d1 100644 --- a/src/main/java/jcifs/pac/kerberos/KerberosEncData.java +++ b/src/main/java/jcifs/pac/kerberos/KerberosEncData.java @@ -41,14 +41,7 @@ import javax.crypto.spec.SecretKeySpec; import javax.security.auth.kerberos.KerberosKey; -import org.bouncycastle.asn1.ASN1InputStream; -import org.bouncycastle.asn1.ASN1Integer; -import org.bouncycastle.asn1.ASN1TaggedObject; -import org.bouncycastle.asn1.DERApplicationSpecific; -import org.bouncycastle.asn1.DERGeneralString; -import org.bouncycastle.asn1.DEROctetString; -import org.bouncycastle.asn1.DERTaggedObject; -import org.bouncycastle.asn1.DLSequence; +import org.bouncycastle.asn1.*; import jcifs.pac.ASN1Util; import jcifs.pac.PACDecodingException; @@ -66,10 +59,10 @@ public class KerberosEncData { public KerberosEncData ( byte[] token, Map keys ) throws PACDecodingException { ASN1InputStream stream = new ASN1InputStream(new ByteArrayInputStream(token)); - DERApplicationSpecific derToken; + ASN1TaggedObject derToken; try { - derToken = ASN1Util.as(DERApplicationSpecific.class, stream); - if ( !derToken.isConstructed() ) + derToken = ASN1Util.as(ASN1TaggedObject.class, stream); + if ( derToken.getTagClass() != BERTags.APPLICATION ) throw new PACDecodingException("Malformed kerberos ticket"); stream.close(); } @@ -77,11 +70,9 @@ public KerberosEncData ( byte[] token, Map keys ) throws P throw new PACDecodingException("Malformed kerberos ticket", e); } - stream = new ASN1InputStream(new ByteArrayInputStream(derToken.getContents())); - DLSequence sequence; + ASN1Sequence sequence; try { - sequence = ASN1Util.as(DLSequence.class, stream); - stream.close(); + sequence = ASN1Util.as(ASN1Sequence.class, derToken.getBaseObject()); } catch ( IOException e ) { throw new PACDecodingException("Malformed kerberos ticket", e); @@ -101,8 +92,8 @@ public KerberosEncData ( byte[] token, Map keys ) throws P this.userRealm = derRealm.getString(); break; case 3: // Principal - DLSequence principalSequence = ASN1Util.as(DLSequence.class, tagged); - DLSequence nameSequence = ASN1Util.as(DLSequence.class, ASN1Util.as(DERTaggedObject.class, principalSequence, 1)); + ASN1Sequence principalSequence = ASN1Util.as(ASN1Sequence.class, tagged); + ASN1Sequence nameSequence = ASN1Util.as(ASN1Sequence.class, ASN1Util.as(ASN1TaggedObject.class, principalSequence, 1)); StringBuilder nameBuilder = new StringBuilder(); Enumeration parts = nameSequence.getObjects(); @@ -134,10 +125,10 @@ public KerberosEncData ( byte[] token, Map keys ) throws P // DERGeneralizedTime.class); break; case 9: // Host Addresses - DLSequence adressesSequence = ASN1Util.as(DLSequence.class, tagged); + ASN1Sequence adressesSequence = ASN1Util.as(ASN1Sequence.class, tagged); Enumeration adresses = adressesSequence.getObjects(); while ( adresses.hasMoreElements() ) { - DLSequence addressSequence = ASN1Util.as(DLSequence.class, adresses); + ASN1Sequence addressSequence = ASN1Util.as(ASN1Sequence.class, adresses); ASN1Integer addressType = ASN1Util.as(ASN1Integer.class, addressSequence, 0); DEROctetString addressOctets = ASN1Util.as(DEROctetString.class, addressSequence, 1); @@ -153,14 +144,14 @@ public KerberosEncData ( byte[] token, Map keys ) throws P } break; case 10: // Authorization Data - DLSequence authSequence = ASN1Util.as(DLSequence.class, tagged); + ASN1Sequence authSequence = ASN1Util.as(ASN1Sequence.class, tagged); this.userAuthorizations = new ArrayList<>(); Enumeration authElements = authSequence.getObjects(); while ( authElements.hasMoreElements() ) { - DLSequence authElement = ASN1Util.as(DLSequence.class, authElements); - ASN1Integer authType = ASN1Util.as(ASN1Integer.class, ASN1Util.as(DERTaggedObject.class, authElement, 0)); - DEROctetString authData = ASN1Util.as(DEROctetString.class, ASN1Util.as(DERTaggedObject.class, authElement, 1)); + ASN1Sequence authElement = ASN1Util.as(ASN1Sequence.class, authElements); + ASN1Integer authType = ASN1Util.as(ASN1Integer.class, ASN1Util.as(ASN1TaggedObject.class, authElement, 0)); + DEROctetString authData = ASN1Util.as(DEROctetString.class, ASN1Util.as(ASN1TaggedObject.class, authElement, 1)); this.userAuthorizations.addAll(KerberosAuthData.parse(authType.getValue().intValue(), authData.getOctets(), keys)); } diff --git a/src/main/java/jcifs/pac/kerberos/KerberosRelevantAuthData.java b/src/main/java/jcifs/pac/kerberos/KerberosRelevantAuthData.java index 7177fd2..2fb4acf 100644 --- a/src/main/java/jcifs/pac/kerberos/KerberosRelevantAuthData.java +++ b/src/main/java/jcifs/pac/kerberos/KerberosRelevantAuthData.java @@ -26,11 +26,7 @@ import javax.security.auth.kerberos.KerberosKey; -import org.bouncycastle.asn1.ASN1InputStream; -import org.bouncycastle.asn1.ASN1Integer; -import org.bouncycastle.asn1.DEROctetString; -import org.bouncycastle.asn1.DERTaggedObject; -import org.bouncycastle.asn1.DLSequence; +import org.bouncycastle.asn1.*; import jcifs.pac.ASN1Util; import jcifs.pac.PACDecodingException; @@ -43,10 +39,10 @@ public class KerberosRelevantAuthData extends KerberosAuthData { public KerberosRelevantAuthData ( byte[] token, Map keys ) throws PACDecodingException { - DLSequence authSequence; + ASN1Sequence authSequence; try { try ( ASN1InputStream stream = new ASN1InputStream(new ByteArrayInputStream(token)) ) { - authSequence = ASN1Util.as(DLSequence.class, stream); + authSequence = ASN1Util.as(ASN1Sequence.class, stream); } } catch ( IOException e ) { @@ -56,9 +52,9 @@ public KerberosRelevantAuthData ( byte[] token, Map keys ) this.authorizations = new ArrayList<>(); Enumeration authElements = authSequence.getObjects(); while ( authElements.hasMoreElements() ) { - DLSequence authElement = ASN1Util.as(DLSequence.class, authElements); - ASN1Integer authType = ASN1Util.as(ASN1Integer.class, ASN1Util.as(DERTaggedObject.class, authElement, 0)); - DEROctetString authData = ASN1Util.as(DEROctetString.class, ASN1Util.as(DERTaggedObject.class, authElement, 1)); + ASN1Sequence authElement = ASN1Util.as(ASN1Sequence.class, authElements); + ASN1Integer authType = ASN1Util.as(ASN1Integer.class, ASN1Util.as(ASN1TaggedObject.class, authElement, 0)); + DEROctetString authData = ASN1Util.as(DEROctetString.class, ASN1Util.as(ASN1TaggedObject.class, authElement, 1)); this.authorizations.addAll(KerberosAuthData.parse(authType.getValue().intValue(), authData.getOctets(), keys)); } diff --git a/src/main/java/jcifs/pac/kerberos/KerberosTicket.java b/src/main/java/jcifs/pac/kerberos/KerberosTicket.java index 3eb15d5..8861538 100644 --- a/src/main/java/jcifs/pac/kerberos/KerberosTicket.java +++ b/src/main/java/jcifs/pac/kerberos/KerberosTicket.java @@ -28,13 +28,7 @@ import javax.security.auth.kerberos.KerberosKey; import javax.security.auth.login.LoginException; -import org.bouncycastle.asn1.ASN1InputStream; -import org.bouncycastle.asn1.ASN1Integer; -import org.bouncycastle.asn1.ASN1TaggedObject; -import org.bouncycastle.asn1.DERGeneralString; -import org.bouncycastle.asn1.DEROctetString; -import org.bouncycastle.asn1.DERTaggedObject; -import org.bouncycastle.asn1.DLSequence; +import org.bouncycastle.asn1.*; import jcifs.pac.ASN1Util; import jcifs.pac.PACDecodingException; @@ -52,10 +46,10 @@ public KerberosTicket ( byte[] token, byte apOptions, KerberosKey[] keys ) throw if ( token.length <= 0 ) throw new PACDecodingException("Empty kerberos ticket"); - DLSequence sequence; + ASN1Sequence sequence; try { try ( ASN1InputStream stream = new ASN1InputStream(new ByteArrayInputStream(token)) ) { - sequence = ASN1Util.as(DLSequence.class, stream); + sequence = ASN1Util.as(ASN1Sequence.class, stream); } } catch ( IOException e ) { @@ -77,8 +71,8 @@ public KerberosTicket ( byte[] token, byte apOptions, KerberosKey[] keys ) throw this.serverRealm = derRealm.getString(); break; case 2:// Principal - DLSequence principalSequence = ASN1Util.as(DLSequence.class, tagged); - DLSequence nameSequence = ASN1Util.as(DLSequence.class, ASN1Util.as(DERTaggedObject.class, principalSequence, 1)); + ASN1Sequence principalSequence = ASN1Util.as(ASN1Sequence.class, tagged); + ASN1Sequence nameSequence = ASN1Util.as(ASN1Sequence.class, ASN1Util.as(ASN1TaggedObject.class, principalSequence, 1)); StringBuilder nameBuilder = new StringBuilder(); Enumeration parts = nameSequence.getObjects(); @@ -92,9 +86,9 @@ public KerberosTicket ( byte[] token, byte apOptions, KerberosKey[] keys ) throw this.serverPrincipalName = nameBuilder.toString(); break; case 3:// Encrypted part - DLSequence encSequence = ASN1Util.as(DLSequence.class, tagged); - ASN1Integer encType = ASN1Util.as(ASN1Integer.class, ASN1Util.as(DERTaggedObject.class, encSequence, 0)); - DEROctetString encOctets = ASN1Util.as(DEROctetString.class, ASN1Util.as(DERTaggedObject.class, encSequence, 2)); + ASN1Sequence encSequence = ASN1Util.as(ASN1Sequence.class, tagged); + ASN1Integer encType = ASN1Util.as(ASN1Integer.class, ASN1Util.as(ASN1TaggedObject.class, encSequence, 0)); + DEROctetString encOctets = ASN1Util.as(DEROctetString.class, ASN1Util.as(ASN1TaggedObject.class, encSequence, 2)); byte[] crypt = encOctets.getOctets(); if ( keys == null ) { diff --git a/src/main/java/jcifs/pac/kerberos/KerberosToken.java b/src/main/java/jcifs/pac/kerberos/KerberosToken.java index e2f70b6..d76c241 100644 --- a/src/main/java/jcifs/pac/kerberos/KerberosToken.java +++ b/src/main/java/jcifs/pac/kerberos/KerberosToken.java @@ -22,9 +22,7 @@ import javax.security.auth.kerberos.KerberosKey; -import org.bouncycastle.asn1.ASN1InputStream; -import org.bouncycastle.asn1.ASN1ObjectIdentifier; -import org.bouncycastle.asn1.DERApplicationSpecific; +import org.bouncycastle.asn1.*; import jcifs.pac.ASN1Util; import jcifs.pac.PACDecodingException; @@ -46,18 +44,21 @@ public KerberosToken ( byte[] token, KerberosKey[] keys ) throws PACDecodingExce if ( token.length <= 0 ) throw new PACDecodingException("Empty kerberos token"); - try { - ASN1InputStream stream = new ASN1InputStream(new ByteArrayInputStream(token)); - DERApplicationSpecific derToken = ASN1Util.as(DERApplicationSpecific.class, stream); - if ( derToken == null || !derToken.isConstructed() ) - throw new PACDecodingException("Malformed kerberos token"); - stream.close(); + byte[] content; + try ( ASN1InputStream stream = new ASN1InputStream(token) ) { + content = ASN1Util.readUnparsedTagged(0, 0x8000, stream); + }catch ( IOException e ) { + throw new PACDecodingException("Malformed kerberos token", e); + } - stream = new ASN1InputStream(new ByteArrayInputStream(derToken.getContents())); - ASN1ObjectIdentifier kerberosOid = ASN1Util.as(ASN1ObjectIdentifier.class, stream); + try ( ASN1InputStream stream = new ASN1InputStream(content) ) { + + ASN1ObjectIdentifier kerberosOid = (ASN1ObjectIdentifier) stream.readObject(); if ( !kerberosOid.getId().equals(KerberosConstants.KERBEROS_OID) ) throw new PACDecodingException("Not a kerberos token"); + + // yes, there really is non ASN.1/DER data inside the tagged object int read = 0; int readLow = stream.read() & 0xff; int readHigh = stream.read() & 0xff; @@ -65,13 +66,12 @@ public KerberosToken ( byte[] token, KerberosKey[] keys ) throws PACDecodingExce if ( read != 0x01 ) throw new PACDecodingException("Malformed kerberos token"); - DERApplicationSpecific krbToken = ASN1Util.as(DERApplicationSpecific.class, stream); - if ( krbToken == null || !krbToken.isConstructed() ) + ASN1TaggedObject mechToken = ASN1Util.as(ASN1TaggedObject.class, stream.readObject()); + if ( mechToken == null || mechToken.getTagClass() != BERTags.APPLICATION || + !(mechToken.getBaseObject() instanceof ASN1Sequence) ) throw new PACDecodingException("Malformed kerberos token"); - stream.close(); - - this.apRequest = new KerberosApRequest(krbToken.getContents(), keys); + this.apRequest = new KerberosApRequest((ASN1Sequence) mechToken.getBaseObject(), keys); } catch ( IOException e ) { throw new PACDecodingException("Malformed kerberos token", e); diff --git a/src/main/java/jcifs/spnego/NegTokenInit.java b/src/main/java/jcifs/spnego/NegTokenInit.java index 87c7137..c66099b 100644 --- a/src/main/java/jcifs/spnego/NegTokenInit.java +++ b/src/main/java/jcifs/spnego/NegTokenInit.java @@ -142,7 +142,7 @@ public byte[] toByteArray () { ev.add(new DERTaggedObject(true, 0, new DERSequence(fields))); ByteArrayOutputStream collector = new ByteArrayOutputStream(); ASN1OutputStream der = ASN1OutputStream.create(collector, ASN1Encoding.DER); - DERApplicationSpecific derApplicationSpecific = new DERApplicationSpecific(0, ev); + DERTaggedObject derApplicationSpecific = new DERTaggedObject(false, BERTags.APPLICATION, 0, new DERSequence(ev)); der.writeObject(derApplicationSpecific); return collector.toByteArray(); } @@ -156,18 +156,19 @@ public byte[] toByteArray () { protected void parse ( byte[] token ) throws IOException { try ( ASN1InputStream is = new ASN1InputStream(token) ) { - ASN1ApplicationSpecific constructed = (ASN1ApplicationSpecific) is.readObject(); - if ( constructed == null || !constructed.isConstructed() ) - throw new IOException( - "Malformed SPNEGO token " + constructed - + ( constructed != null ? " " + constructed.isConstructed() + " " + constructed.getApplicationTag() : "" )); - - try ( ASN1InputStream der = new ASN1InputStream(constructed.getContents()) ) { - ASN1ObjectIdentifier spnego = (ASN1ObjectIdentifier) der.readObject(); + ASN1TaggedObject constructed = (ASN1TaggedObject) is.readObject(); + if ( constructed == null || constructed.getTagClass() != BERTags.APPLICATION || + !(constructed.getBaseObject() instanceof ASN1Sequence) ) + throw new IOException("Malformed SPNEGO token " + constructed); + + ASN1Sequence vec = (ASN1Sequence) constructed.getBaseObject(); + + + ASN1ObjectIdentifier spnego = (ASN1ObjectIdentifier) vec.getObjectAt(0); if ( !SPNEGO_OID.equals(spnego) ) { throw new IOException("Malformed SPNEGO token, OID " + spnego); } - ASN1TaggedObject tagged = (ASN1TaggedObject) der.readObject(); + ASN1TaggedObject tagged = (ASN1TaggedObject) vec.getObjectAt(1); if ( tagged.getTagNo() != 0 ) { throw new IOException("Malformed SPNEGO token: tag " + tagged.getTagNo() + " " + tagged); } @@ -194,7 +195,7 @@ protected void parse ( byte[] token ) throws IOException { break; case 3: - if ( ! ( tagged.getObject() instanceof DEROctetString ) ) { + if ( ! ( tagged.getBaseObject() instanceof DEROctetString ) ) { break; } case 4: @@ -205,7 +206,6 @@ protected void parse ( byte[] token ) throws IOException { throw new IOException("Malformed token field."); } } - } } } diff --git a/src/test/java/jcifs/tests/PACTest.java b/src/test/java/jcifs/tests/PACTest.java index a9173c5..e1bf0df 100644 --- a/src/test/java/jcifs/tests/PACTest.java +++ b/src/test/java/jcifs/tests/PACTest.java @@ -18,7 +18,10 @@ package jcifs.tests; +import java.io.File; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -28,7 +31,15 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.security.auth.kerberos.KerberosKey; - +import javax.security.auth.kerberos.KerberosPrincipal; +import javax.security.auth.kerberos.KeyTab; + +import jcifs.pac.Pac; +import jcifs.pac.PacLogonInfo; +import jcifs.pac.kerberos.KerberosEncData; +import jcifs.pac.kerberos.KerberosPacAuthData; +import jcifs.pac.kerberos.KerberosToken; +import jcifs.spnego.NegTokenInit; import org.bouncycastle.util.encoders.Hex; import org.junit.Assert; import org.junit.Test; @@ -201,6 +212,7 @@ public void testPACArcfourChecksum () throws GeneralSecurityException { } + @Test public void testAES128Checksum () throws GeneralSecurityException { String data = "eight nine ten eleven twelve thirteen"; @@ -295,4 +307,77 @@ private static void verifyNfold ( int n, String string, String expect ) { byte[] expanded = PacMac.expandNFold(string.getBytes(StandardCharsets.US_ASCII), n / 8); Assert.assertEquals(expect, Hexdump.toHexString(expanded).toLowerCase(Locale.ROOT)); } + + + + @Test + public void testParseSPENGOToken() throws Exception { + /* + negTokenInit + mechTypes: 2 items + MechType: 1.2.840.113554.1.2.2 (KRB5 - Kerberos 5) + MechType: 1.2.840.48018.1.2.2 (MS KRB5 - Microsoft Kerberos 5) + Padding: 6 + reqFlags: c0 + mechToken: 60820b2906092a864886f71201020201006e820b1830820b14a003020105a10302010ea2… + krb5_blob: 60820b2906092a864886f71201020201006e820b1830820b14a003020105a10302010ea2… + KRB5 OID: 1.2.840.113554.1.2.2 (KRB5 - Kerberos 5) + krb5_tok_id: KRB5_AP_REQ (0x0001) + Kerberos + ap-req + pvno: 5 + msg-type: krb-ap-req (14) + Padding: 0 + ap-options: 20000000 + ticket + tkt-vno: 5 + realm: W2K19SINGLE.SPRINGFIELD + sname + name-type: kRB5-NT-UNKNOWN (0) + sname-string: 2 items + SNameString: cifs + SNameString: fakeserver.w2k19single.springfield + enc-part + etype: eTYPE-ARCFOUR-HMAC-MD5 (23) + kvno: 5 + cipher: 508c1fb7944c336ead0edf4536ba1ecf7102923d9dc9e0ab1d5c73d0d3bb4ca6a1120cdd… + authenticator + etype: eTYPE-ARCFOUR-HMAC-MD5 (23) + cipher: e24ff60648505a37d583d77e20a845158b7cfe8c652ab16d0eeeb4c8700370e5d640bbdd… + + */ + + + Path krbConfig = Files.createTempFile("krb5", ".conf"); + Files.write(krbConfig,Arrays.asList("[libdefaults]", "allow_weak_crypto=true")); + System.setProperty("java.security.krb5.conf", krbConfig.toString()); + + + byte[] keyTab = Hex.decode("0502000000620002001757324b313953494e474c452e535052494e474649454c44000463696673002266616b657365727665722e77326b313973696e676c652e737072696e676669656c64000000010000000005001700108846f7eaee8fb117ad06bdd830b7586c"); + + // kerberos mech token with ticket for fake service, RC4 service key in keyTab above + byte[] mechToken = Hex.decode("60820b3c06062b0601050502a0820b3030820b2ca018301606092a864886f71201020206092a864882f712010202a104030206c0a2820b0804820b0460820b0006092a864886f71201020201006e820aef30820aeba003020105a10302010ea20703050020000000a38204636182045f3082045ba003020105a1191b1757324b313953494e474c452e535052494e474649454c44a2353033a003020100a12c302a1b04636966731b2266616b657365727665722e77326b313973696e676c652e737072696e676669656c64a3820400308203fca003020117a103020105a28203ee048203ea508c1fb7944c336ead0edf4536ba1ecf7102923d9dc9e0ab1d5c73d0d3bb4ca6a1120cdd3bd095c7eaecff903c7cf9cff53f62f9bb447afc2d3a43898eb7c8e70b13c58951de596fd4f4b0c23d0eed2519ccb9c120e86c92a94de499f78526c3d3f6bf673c76858d53e32fb520f02f8a992a120137ba169c097e8d5f307ac1e6c0dfd998e925f49baa721f7922e40a8c7f4848231d8fd0b1b426e4ae734e78688cac8c9b67e5d4bf0f7487f0acb6d35b6fb784bff165cb9707bbb8c914f8e22dfb414a7bdebc26ca466f60222e9b85267aad8419416c2792bf80074af0935c74d296bd5edce4b479e31c8e18e62e9afa0ec832532dc8ba60bcca4dd4b56b5f64b83dfd7d80c5755983aa3be23c6fc1fa8a9fdc67dca873cb645cada8e106188c7f9d48cb7a4d628beb6460a431b60ffe99fc0f6a4aa7354007c025051663fe3d67c7a89e7d30214899e02a66c06f4cd61bbe542b1b3bf728180a310866b02b5d61003c40d7259552df7dd7c222b207904e908c3068236949cd80925def2b8fb83089cf4aabcec81b8465e261dd14a683600574f676dfe0d0e631119e1a72d44253ae82becdf21772cc76127205e72392bf478fc45042bcb7b21e1ea0e70fc9424ecb4148c2e3a0becf7573b5a945775c0f31c69063f71f066ad71a23f5410b0a6c442516cfea346517a511e371cd7e60dc453fe7bae7f05186bd010278ce4089bebb12f00b0f973225fd69c5e6f37bf8b4aa3876c42c29cb7c7ff369956ff7acfb49de8fb23017100436ea235d09d9ad7f02a73ecc8ed1fa858acc802b5ffd52d49f023d07ad659883e6b777f954120041364a53bd8e3fdaae0ad19616743945a172f91999e03e3eb39833768372974fefd8406774b2ccfbe28882ce2737268c6909dd8563f3a93dff4f5daf00926f34bf6d8fa486ed66664a09e9d2032f6eb2a45691784330b6c3134d886bcac8b524b5afaf3b566890aaa69a50d6df7587605e1f8aa7d5ad4717db6567a7e4bf68b6998afeb63098f5579544fe847d51ac2582ea0deeea4d61abc98c1a5cd161a150f5e7694d78f525a14e3c4e09a0ce49bf089b8b6e6910fa1d5b2ef0d007b855a1510071e614a04530c774e061dcdcdd5c31d93aa03a1279528234a4af7f8d0a99c89a169a1e887f83b63f37328fc97f0132bb124acbe5100830fd030bec3e8dbc18e2b0ec2ef0697966168e04dec57a16149d2ba7ae9c952a6eb2eb336b62eb6a4c5b53356d6b24e0dbd0b5fa5ca8ad37bf659775b002ba65d5fb597824378011c5edb5e6bf71be0cee261aa469329aee73ad71c9f96bf68bf3255956b630e647a37ee9ad76941ac683531c1cd9ef4017df91188b55913cb95386a508b883041c73ce4e5ec4add613d5ffbb79a8daa1350e36953e4ab04cb4beb0a482066d30820669a003020117a28206600482065ce24ff60648505a37d583d77e20a845158b7cfe8c652ab16d0eeeb4c8700370e5d640bbdd35bd3b240612ada43935932626ae5bc9cb35ade46faf8da687b8e8433b7f0168c5bb13c6e9fcde63061ecd488284720cb512793b2e951330cd211af1dc06324b8029ee6a5fd60c68c3fc3d7bc6b070de0f72629dacb3cfcf7efc1b17abea6e92494ee87d7a797a752da2f0ca3927b620d52340cc7b216a1d87f39ba971050b580efb32d4921076d37080a84882122dcfb6f5f683530e681faec88e73cf0802e16b7fdd27b2693ffa19c689b1aab8e39465d71a6aa491105928454b8d5b64233051a02e200847d9e95f7a5dd268c73c5748ed27cc8d87797a146935a5eda8682681b1928a99ca8152ef30e14996732c9713cc7f84fe693ca38c634ca06d8ea2cf7267ae7c5b8de422dbe88f24edb833b24963cb77e1ce26b9a1040185a02af9cc6e1749e162d085202428bc1ead6829355b6774527de4fef78b43ded7f859bafeba992b4d454fb317db4a7a33d857d1a296d163f09ae310fac4b4ac35066c2efaec9b75d93ea3a5cf83153a7d75b0a5c718f48f0b1114c14b918d605b0c859751d8b86a2479e4e042c9650fd55bce5a90e96db83b715eb3445d9d143cab9154019816732105dcf009a03e9d2966f7af4291a4812379e36811a7ceb17a531b19a8fe5a085d45641952b95b70c193e078d707e6618ae7541f40cdccafa92681fcb4bc22eb7c67d08a3ca97df5c3b9ef2cce3e592db2914fb7181f1d46bc8f7bf0f1ef2d6033deeb0d600da3ef340a2f3dd121ebadc72b70cad6d9cb01985f6dc67f70b85b0e7f5eb706c86663703b5c48807803ede643f580752fd68be0b7feabd8ea0b9576cb5167e0954d470bc1ca94160b9b96e30f948a77ecf0987977a251a99274aeea64bba1c9bf9dc4d3075be9cf1c10c6935104fe9f7ad6718d8439f24e26871234ecd4c5ae1491a5d87fa4581fc5eb20ecf22702a4697136748f194ef4a13ef0ed09f8873ea9eaba61710d03de38e8ac35109493b804bb958325a04bb91f46dff30db347f421819b476c3280a5fbca5219a9df2ee82d63a194b813e5b5b6a3315d28b203cb814daf1ebc8562aa8d54462ab3bac42dcc066412aa6e31b41e46e72ffbd846cdcc34dc61b09e06ac1e86debbaf52123b61c58810278143c26838ed858e889a5b41bf88155beb2bbb381e74f6c911682b1416838805d5f17c6b4742058a65429f5b4c1052704e1a3046c8d1dc5572ad62e4c7abb6cb5d76b349d737be5655dc48d1491130f140feb57a61d1c20a2f8ecd22f1802f87d83393482b4dc0c0b89d5ef60e75ee5cc09958b5624f6166c331615a935905c9c345f529567a422e665dbd1b01ce71bd05e66da06dc4cdafebd1f05911cdefc50a6a2dfb9fe85731b5caf2e81ff358553663f69301ed1474437da8f3d78c12731a194990f170f2993ec590b32677d87c2b2f64c87267791ada998047303fc749e2681fd3cb908be1be1b924f250a3434e159d6eafae6d3a21405232bc4a37bfa44149790427e6b6ed6f0c0f47efc472fdeb0cfab2005e9336415697e6b31339a6cf3aa9b490864454c65b1086d6535a735bc6cc722fe8e0d1e69b0c3a65922662a7a0e53c0b11df447c9803ceefd2e26c413cb5acc24b29e4718275601ed3ecf0305a714355298987ba5b07a7d871d4c81b6e09c0c6486f8d2ebda5fb82a926e1793a1d0fa0ce6cd999e1367ab775d5721ac0f4a490bc6c8aed3c34be3c2856c4349caaeedd6db7b21c022bd9d6a9360be13093f762ccfeab494f3fe8d69ee47aea3f987dea36c4940ac402e8a7db755a139d9a53f6414eccd56270bf5d96bced2025f8fc545f5630837209e4f3ef5652ca328339f02644948b703da31ad58053dd43aca01a4069025044709e825f87f72ef9ccf8ec544e74b537f19540f09d58f00eafc32614a89b7887a7ec261d06d460623d2adbebe561d0e866471326e7a1441464a4c21ca1eebbe29e9fbb8a1666d8b858c0c237cb2d60344b7b861cebbfa834a76f1b95a92d333bbcfe5e0405b3428434b112558744c2e261a9bf940223930a2ed443a0181d43be4149524681f91e417d36e7dd7749ffa3a30b4c35d56fed9673a638028ae3688089b3dc818f1ffb38316b65781362fe624374ccc570624f52eede4ad601aeeb0f01e12368ea8ecbfc8ccc797be6207d9140994c4a4dc4500df00f90277bbca248c246ea8f11e02697c2305787d12a0beed369ed4f57d3ec26eea5e3ee9b29959df6398848811e9e9e05677389dee2ef4b8"); + NegTokenInit negInit = new NegTokenInit(mechToken); + byte[] krbToken = negInit.getMechanismToken(); + + + Path p = Files.createTempFile("fakeserver", ".keytab"); + try { + Files.write(p, keyTab); + KeyTab kt = KeyTab.getInstance(p.toFile()); + KerberosKey[] keys = kt.getKeys(new KerberosPrincipal("cifs/fakeserver.w2k19single.springfield@W2K19SINGLE.SPRINGFIELD", KerberosPrincipal.KRB_NT_PRINCIPAL)); + KerberosToken tok = new KerberosToken(krbToken, keys); + + KerberosEncData ed = tok.getTicket().getEncData(); + Assert.assertEquals(1, ed.getUserAuthorizations().size()); + + KerberosPacAuthData pacData = (KerberosPacAuthData) ed.getUserAuthorizations().get(0); + Pac pac = pacData.getPac(); + PacLogonInfo li = pac.getLogonInfo(); + Assert.assertEquals("test1", li.getUserName()); + } finally { + Files.deleteIfExists(p); + Files.deleteIfExists(krbConfig); + } + } }