Skip to content

Commit 7f9df3e

Browse files
rdicrocepeterdettman
authored andcommitted
TLS: RFC 7250 Raw Public Keys
- see #820
1 parent ce74f3c commit 7f9df3e

21 files changed

+1603
-462
lines changed

tls/src/main/java/org/bouncycastle/tls/AbstractTlsClient.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,16 @@ protected Vector getTrustedCAIndication()
179179
return null;
180180
}
181181

182+
protected short[] getAllowedClientCertificateTypes()
183+
{
184+
return null;
185+
}
186+
187+
protected short[] getAllowedServerCertificateTypes()
188+
{
189+
return null;
190+
}
191+
182192
public void init(TlsClientContext context)
183193
{
184194
this.context = context;
@@ -337,6 +347,33 @@ public Hashtable getClientExtensions()
337347
}
338348
}
339349

350+
/*
351+
* RFC 7250 4.1:
352+
*
353+
* If the client has no remaining certificate types to send in
354+
* the client hello, other than the default X.509 type, it MUST omit the
355+
* client_certificate_type extension in the client hello.
356+
*/
357+
short[] clientCertTypes = getAllowedClientCertificateTypes();
358+
if (clientCertTypes != null && (clientCertTypes.length > 1 || clientCertTypes[0] != CertificateType.X509))
359+
{
360+
TlsExtensionsUtils.addClientCertificateTypeExtensionClient(clientExtensions, clientCertTypes);
361+
}
362+
363+
/*
364+
* RFC 7250 4.1:
365+
*
366+
* If the client has no remaining certificate types to send in
367+
* the client hello, other than the default X.509 certificate type, it
368+
* MUST omit the entire server_certificate_type extension from the
369+
* client hello.
370+
*/
371+
short[] serverCertTypes = getAllowedServerCertificateTypes();
372+
if (serverCertTypes != null && (serverCertTypes.length > 1 || serverCertTypes[0] != CertificateType.X509))
373+
{
374+
TlsExtensionsUtils.addServerCertificateTypeExtensionClient(clientExtensions, serverCertTypes);
375+
}
376+
340377
return clientExtensions;
341378
}
342379

tls/src/main/java/org/bouncycastle/tls/AbstractTlsServer.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,16 @@ protected boolean shouldSelectProtocolNameEarly()
229229
return true;
230230
}
231231

232+
protected boolean preferLocalClientCertificateTypes()
233+
{
234+
return false;
235+
}
236+
237+
protected short[] getAllowedClientCertificateTypes()
238+
{
239+
return null;
240+
}
241+
232242
public void init(TlsServerContext context)
233243
{
234244
this.context = context;
@@ -523,6 +533,64 @@ else if (null != this.certificateStatusRequest && allowCertificateStatus())
523533
TlsExtensionsUtils.addMaxFragmentLengthExtension(serverExtensions, this.maxFragmentLengthOffered);
524534
}
525535

536+
// RFC 7250 4.2 for server_certificate_type
537+
short[] serverCertTypes = TlsExtensionsUtils.getServerCertificateTypeExtensionClient(clientExtensions);
538+
if (serverCertTypes != null)
539+
{
540+
TlsCredentials credentials = getCredentials();
541+
542+
if (credentials == null ||
543+
!TlsUtils.contains(serverCertTypes, 0, serverCertTypes.length, credentials.getCertificate().getCertificateType()))
544+
{
545+
// outcome 2: we support the extension but have no common types
546+
throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
547+
}
548+
549+
// outcome 3: we support the extension and have a common type
550+
TlsExtensionsUtils.addServerCertificateTypeExtensionServer(serverExtensions, credentials.getCertificate().getCertificateType());
551+
}
552+
553+
// RFC 7250 4.2 for client_certificate_type
554+
short[] remoteClientCertTypes = TlsExtensionsUtils.getClientCertificateTypeExtensionClient(clientExtensions);
555+
if (remoteClientCertTypes != null)
556+
{
557+
short[] localClientCertTypes = getAllowedClientCertificateTypes();
558+
if (localClientCertTypes != null)
559+
{
560+
short[] preferredTypes;
561+
short[] nonPreferredTypes;
562+
if (preferLocalClientCertificateTypes())
563+
{
564+
preferredTypes = localClientCertTypes;
565+
nonPreferredTypes = remoteClientCertTypes;
566+
}
567+
else
568+
{
569+
preferredTypes = remoteClientCertTypes;
570+
nonPreferredTypes = localClientCertTypes;
571+
}
572+
573+
short selectedType = -1;
574+
for (int i = 0; i < preferredTypes.length; i++)
575+
{
576+
if (TlsUtils.contains(nonPreferredTypes, 0, nonPreferredTypes.length, preferredTypes[i]))
577+
{
578+
selectedType = preferredTypes[i];
579+
break;
580+
}
581+
}
582+
583+
if (selectedType == -1)
584+
{
585+
// outcome 2: we support the extension but have no common types
586+
throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
587+
}
588+
589+
// outcome 3: we support the extension and have a common type
590+
TlsExtensionsUtils.addClientCertificateTypeExtensionServer(serverExtensions, selectedType);
591+
} // else outcome 1: we don't support the extension
592+
}
593+
526594
return serverExtensions;
527595
}
528596

tls/src/main/java/org/bouncycastle/tls/Certificate.java

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public class Certificate
3333
public static class ParseOptions
3434
{
3535
private int maxChainLength = Integer.MAX_VALUE;
36+
private short certificateType = CertificateType.X509;
3637

3738
public int getMaxChainLength()
3839
{
@@ -44,6 +45,20 @@ public ParseOptions setMaxChainLength(int maxChainLength)
4445
this.maxChainLength = maxChainLength;
4546
return this;
4647
}
48+
49+
public short getCertificateType()
50+
{
51+
return certificateType;
52+
}
53+
54+
/**
55+
* Set the {@link CertificateType} according to the IANA TLS Certificate Types registry
56+
*/
57+
public ParseOptions setCertificateType(short certificateType)
58+
{
59+
this.certificateType = certificateType;
60+
return this;
61+
}
4762
}
4863

4964
private static CertificateEntry[] convert(TlsCertificate[] certificateList)
@@ -64,14 +79,20 @@ private static CertificateEntry[] convert(TlsCertificate[] certificateList)
6479

6580
protected final byte[] certificateRequestContext;
6681
protected final CertificateEntry[] certificateEntryList;
82+
protected final short certificateType;
6783

6884
public Certificate(TlsCertificate[] certificateList)
6985
{
7086
this(null, convert(certificateList));
7187
}
7288

73-
// TODO[tls13] Prefer to manage the certificateRequestContext internally only?
7489
public Certificate(byte[] certificateRequestContext, CertificateEntry[] certificateEntryList)
90+
{
91+
this(CertificateType.X509, certificateRequestContext, certificateEntryList);
92+
}
93+
94+
// TODO[tls13] Prefer to manage the certificateRequestContext internally only?
95+
public Certificate(short certificateType, byte[] certificateRequestContext, CertificateEntry[] certificateEntryList)
7596
{
7697
if (null != certificateRequestContext && !TlsUtils.isValidUint8(certificateRequestContext.length))
7798
{
@@ -84,6 +105,7 @@ public Certificate(byte[] certificateRequestContext, CertificateEntry[] certific
84105

85106
this.certificateRequestContext = TlsUtils.clone(certificateRequestContext);
86107
this.certificateEntryList = certificateEntryList;
108+
this.certificateType = certificateType;
87109
}
88110

89111
public byte[] getCertificateRequestContext()
@@ -117,7 +139,7 @@ public CertificateEntry[] getCertificateEntryList()
117139

118140
public short getCertificateType()
119141
{
120-
return CertificateType.X509;
142+
return certificateType;
121143
}
122144

123145
public int getLength()
@@ -190,8 +212,13 @@ public void encode(TlsContext context, OutputStream messageOutput, OutputStream
190212
}
191213
}
192214

193-
TlsUtils.checkUint24(totalLength);
194-
TlsUtils.writeUint24((int)totalLength, messageOutput);
215+
// RFC 7250 indicates the raw key is not wrapped in a cert list like X509 is
216+
// but RFC 8446 wraps it in a CertificateEntry, which is inside certificate_list
217+
if (isTLSv13 || certificateType != CertificateType.RawPublicKey)
218+
{
219+
TlsUtils.checkUint24(totalLength);
220+
TlsUtils.writeUint24((int)totalLength, messageOutput);
221+
}
195222

196223
for (int i = 0; i < count; ++i)
197224
{
@@ -242,6 +269,7 @@ public static Certificate parse(ParseOptions options, TlsContext context, InputS
242269
{
243270
final SecurityParameters securityParameters = context.getSecurityParameters();
244271
final boolean isTLSv13 = TlsUtils.isTLSv13(securityParameters.getNegotiatedVersion());
272+
final short certType = options.getCertificateType();
245273

246274
byte[] certificateRequestContext = null;
247275
if (isTLSv13)
@@ -254,7 +282,7 @@ public static Certificate parse(ParseOptions options, TlsContext context, InputS
254282
{
255283
return !isTLSv13 ? EMPTY_CHAIN
256284
: certificateRequestContext.length < 1 ? EMPTY_CHAIN_TLS13
257-
: new Certificate(certificateRequestContext, EMPTY_CERT_ENTRIES);
285+
: new Certificate(certType, certificateRequestContext, EMPTY_CERT_ENTRIES);
258286
}
259287

260288
byte[] certListData = TlsUtils.readFully(totalLength, messageInput);
@@ -272,8 +300,20 @@ public static Certificate parse(ParseOptions options, TlsContext context, InputS
272300
"Certificate chain longer than maximum (" + maxChainLength + ")");
273301
}
274302

275-
byte[] derEncoding = TlsUtils.readOpaque24(buf, 1);
276-
TlsCertificate cert = crypto.createCertificate(derEncoding);
303+
// RFC 7250 indicates the raw key is not wrapped in a cert list like X509 is
304+
// but RFC 8446 wraps it in a CertificateEntry, which is inside certificate_list
305+
byte[] derEncoding;
306+
if (isTLSv13 || certType != CertificateType.RawPublicKey)
307+
{
308+
derEncoding = TlsUtils.readOpaque24(buf, 1);
309+
}
310+
else
311+
{
312+
derEncoding = certListData;
313+
buf.skip(totalLength);
314+
}
315+
316+
TlsCertificate cert = crypto.createCertificate(certType, derEncoding);
277317

278318
if (certificate_list.isEmpty() && endPointHashOutput != null)
279319
{
@@ -297,7 +337,7 @@ public static Certificate parse(ParseOptions options, TlsContext context, InputS
297337
certificateList[i] = (CertificateEntry)certificate_list.elementAt(i);
298338
}
299339

300-
return new Certificate(certificateRequestContext, certificateList);
340+
return new Certificate(certType, certificateRequestContext, certificateList);
301341
}
302342

303343
protected static void calculateEndPointHash(TlsContext context, TlsCertificate cert, byte[] encoding,

tls/src/main/java/org/bouncycastle/tls/DTLSClientProtocol.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,9 @@ protected void processCertificateRequest(ClientHandshakeState state, byte[] body
579579
TlsProtocol.assertEmpty(buf);
580580

581581
state.certificateRequest = TlsUtils.validateCertificateRequest(certificateRequest, state.keyExchange);
582+
583+
state.clientContext.getSecurityParametersHandshake().clientCertificateType =
584+
TlsExtensionsUtils.getClientCertificateTypeExtensionServer(state.serverExtensions, CertificateType.X509);
582585
}
583586

584587
protected void processCertificateStatus(ClientHandshakeState state, byte[] body)
@@ -635,7 +638,7 @@ protected void processServerCertificate(ClientHandshakeState state, byte[] body)
635638
throws IOException
636639
{
637640
state.authentication = TlsUtils.receiveServerCertificate(state.clientContext, state.client,
638-
new ByteArrayInputStream(body));
641+
new ByteArrayInputStream(body), state.serverExtensions);
639642
}
640643

641644
protected void processServerHello(ClientHandshakeState state, byte[] body)

tls/src/main/java/org/bouncycastle/tls/DTLSServerProtocol.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,7 @@ protected void processClientCertificate(ServerHandshakeState state, byte[] body)
640640
ByteArrayInputStream buf = new ByteArrayInputStream(body);
641641

642642
Certificate.ParseOptions options = new Certificate.ParseOptions()
643+
.setCertificateType(TlsExtensionsUtils.getClientCertificateTypeExtensionServer(state.clientExtensions, CertificateType.X509))
643644
.setMaxChainLength(state.server.getMaxCertificateChainLength());
644645

645646
Certificate clientCertificate = Certificate.parse(options, state.serverContext, buf, null);

tls/src/main/java/org/bouncycastle/tls/SecurityParameters.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public class SecurityParameters
5858
Certificate peerCertificate = null;
5959
ProtocolVersion negotiatedVersion = null;
6060
int statusRequestVersion = 0;
61+
short clientCertificateType = -1;
6162

6263
// TODO[tls-ops] Investigate whether we can handle verify data using TlsSecret
6364
byte[] localVerifyData = null;
@@ -367,6 +368,11 @@ public int getStatusRequestVersion()
367368
return statusRequestVersion;
368369
}
369370

371+
public short getClientCertificateType()
372+
{
373+
return clientCertificateType;
374+
}
375+
370376
private static TlsSecret clearSecret(TlsSecret secret)
371377
{
372378
if (null != secret)

tls/src/main/java/org/bouncycastle/tls/TlsClientProtocol.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ protected void handleHandshakeMessage(short type, HandshakeMessageInput buf)
408408
* NOTE: Certificate processing (including authentication) is delayed to allow for a
409409
* possible CertificateStatus message.
410410
*/
411-
this.authentication = TlsUtils.receiveServerCertificate(tlsClientContext, tlsClient, buf);
411+
this.authentication = TlsUtils.receiveServerCertificate(tlsClientContext, tlsClient, buf, serverExtensions);
412412
break;
413413
}
414414
default:
@@ -1469,6 +1469,9 @@ protected void receive13CertificateRequest(ByteArrayInputStream buf, boolean pos
14691469

14701470
this.certificateRequest = certificateRequest;
14711471

1472+
tlsClientContext.getSecurityParametersHandshake().clientCertificateType =
1473+
TlsExtensionsUtils.getClientCertificateTypeExtensionServer(serverExtensions, CertificateType.X509);
1474+
14721475
TlsUtils.establishServerSigAlgs(tlsClientContext.getSecurityParametersHandshake(), certificateRequest);
14731476
}
14741477

@@ -1580,7 +1583,7 @@ protected void receive13ServerCertificate(ByteArrayInputStream buf)
15801583
throw new TlsFatalAlert(AlertDescription.unexpected_message);
15811584
}
15821585

1583-
this.authentication = TlsUtils.receive13ServerCertificate(tlsClientContext, tlsClient, buf);
1586+
this.authentication = TlsUtils.receive13ServerCertificate(tlsClientContext, tlsClient, buf, serverExtensions);
15841587

15851588
// NOTE: In TLS 1.3 we don't have to wait for a possible CertificateStatus message.
15861589
handleServerCertificate();
@@ -1624,6 +1627,9 @@ protected void receiveCertificateRequest(ByteArrayInputStream buf) throws IOExce
16241627
assertEmpty(buf);
16251628

16261629
this.certificateRequest = TlsUtils.validateCertificateRequest(certificateRequest, keyExchange);
1630+
1631+
tlsClientContext.getSecurityParametersHandshake().clientCertificateType =
1632+
TlsExtensionsUtils.getClientCertificateTypeExtensionServer(serverExtensions, CertificateType.X509);
16271633
}
16281634

16291635
protected void receiveNewSessionTicket(ByteArrayInputStream buf)

tls/src/main/java/org/bouncycastle/tls/TlsExtensionsUtils.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -304,11 +304,11 @@ public static short[] getClientCertificateTypeExtensionClient(Hashtable extensio
304304
return extensionData == null ? null : readCertificateTypeExtensionClient(extensionData);
305305
}
306306

307-
public static short getClientCertificateTypeExtensionServer(Hashtable extensions)
307+
public static short getClientCertificateTypeExtensionServer(Hashtable extensions, short defaultValue)
308308
throws IOException
309309
{
310310
byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_client_certificate_type);
311-
return extensionData == null ? -1 : readCertificateTypeExtensionServer(extensionData);
311+
return extensionData == null ? defaultValue : readCertificateTypeExtensionServer(extensionData);
312312
}
313313

314314
public static int[] getCompressCertificateExtension(Hashtable extensions)
@@ -415,11 +415,11 @@ public static short[] getServerCertificateTypeExtensionClient(Hashtable extensio
415415
return extensionData == null ? null : readCertificateTypeExtensionClient(extensionData);
416416
}
417417

418-
public static short getServerCertificateTypeExtensionServer(Hashtable extensions)
418+
public static short getServerCertificateTypeExtensionServer(Hashtable extensions, short defaultValue)
419419
throws IOException
420420
{
421421
byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_server_certificate_type);
422-
return extensionData == null ? -1 : readCertificateTypeExtensionServer(extensionData);
422+
return extensionData == null ? defaultValue : readCertificateTypeExtensionServer(extensionData);
423423
}
424424

425425
public static Vector getServerNameExtensionClient(Hashtable extensions)

tls/src/main/java/org/bouncycastle/tls/TlsServerProtocol.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,6 +1418,7 @@ protected void receive13ClientCertificate(ByteArrayInputStream buf)
14181418
}
14191419

14201420
Certificate.ParseOptions options = new Certificate.ParseOptions()
1421+
.setCertificateType(TlsExtensionsUtils.getClientCertificateTypeExtensionServer(serverExtensions, CertificateType.X509))
14211422
.setMaxChainLength(tlsServer.getMaxCertificateChainLength());
14221423

14231424
Certificate clientCertificate = Certificate.parse(options, tlsServerContext, buf, null);
@@ -1457,6 +1458,7 @@ protected void receiveCertificateMessage(ByteArrayInputStream buf)
14571458
}
14581459

14591460
Certificate.ParseOptions options = new Certificate.ParseOptions()
1461+
.setCertificateType(TlsExtensionsUtils.getClientCertificateTypeExtensionServer(serverExtensions, CertificateType.X509))
14601462
.setMaxChainLength(tlsServer.getMaxCertificateChainLength());
14611463

14621464
Certificate clientCertificate = Certificate.parse(options, tlsServerContext, buf, null);

0 commit comments

Comments
 (0)