87
87
import lombok .AllArgsConstructor ;
88
88
import lombok .NonNull ;
89
89
import lombok .RequiredArgsConstructor ;
90
+ import lombok .Value ;
90
91
import lombok .extern .slf4j .Slf4j ;
91
92
92
93
/**
@@ -119,6 +120,7 @@ public final class FidoMetadataDownloader {
119
120
private final CertStore certStore ;
120
121
@ NonNull private final Clock clock ;
121
122
private final KeyStore httpsTrustStore ;
123
+ private final boolean verifyDownloadsOnly ;
122
124
123
125
/**
124
126
* Begin configuring a {@link FidoMetadataDownloader} instance. See the {@link
@@ -148,6 +150,7 @@ public static class FidoMetadataDownloaderBuilder {
148
150
private CertStore certStore = null ;
149
151
@ NonNull private Clock clock = Clock .systemUTC ();
150
152
private KeyStore httpsTrustStore = null ;
153
+ private boolean verifyDownloadsOnly = false ;
151
154
152
155
public FidoMetadataDownloader build () {
153
156
return new FidoMetadataDownloader (
@@ -165,7 +168,8 @@ public FidoMetadataDownloader build() {
165
168
blobCacheConsumer ,
166
169
certStore ,
167
170
clock ,
168
- httpsTrustStore );
171
+ httpsTrustStore ,
172
+ verifyDownloadsOnly );
169
173
}
170
174
171
175
/**
@@ -611,6 +615,26 @@ public FidoMetadataDownloaderBuilder trustHttpsCerts(@NonNull X509Certificate...
611
615
612
616
return this ;
613
617
}
618
+
619
+ /**
620
+ * If set to <code>true</code>, the BLOB signature will not be verified when loading the BLOB
621
+ * from cache or when explicitly set via {@link Step4#useBlob(String)}. This means that if a
622
+ * BLOB was successfully verified once and written to cache, that cached value will be
623
+ * implicitly trusted when loaded in the future.
624
+ *
625
+ * <p>If set to <code>false</code>, the BLOB signature will always be verified no matter where
626
+ * the BLOB came from. This means that a cached BLOB may become invalid if the BLOB certificate
627
+ * expires, even if the BLOB was successfully verified at the time it was downloaded.
628
+ *
629
+ * <p>The default setting is <code>false</code>.
630
+ *
631
+ * @param verifyDownloadsOnly <code>true</code> if the BLOB signature should be ignored when
632
+ * loading the BLOB from cache or when explicitly set via {@link Step4#useBlob(String)}.
633
+ */
634
+ public FidoMetadataDownloaderBuilder verifyDownloadsOnly (final boolean verifyDownloadsOnly ) {
635
+ this .verifyDownloadsOnly = verifyDownloadsOnly ;
636
+ return this ;
637
+ }
614
638
}
615
639
616
640
/**
@@ -960,7 +984,7 @@ private Optional<MetadataBLOB> loadExplicitBlobOnly(X509Certificate trustRootCer
960
984
FidoMetadataDownloaderException {
961
985
if (blobJwt != null ) {
962
986
return Optional .of (
963
- parseAndVerifyBlob (
987
+ parseAndMaybeVerifyBlob (
964
988
new ByteArray (blobJwt .getBytes (StandardCharsets .UTF_8 )), trustRootCertificate ));
965
989
966
990
} else {
@@ -987,7 +1011,7 @@ private Optional<MetadataBLOB> loadCachedBlobOnly(X509Certificate trustRootCerti
987
1011
return cachedContents .map (
988
1012
cached -> {
989
1013
try {
990
- return parseAndVerifyBlob (cached , trustRootCertificate );
1014
+ return parseAndMaybeVerifyBlob (cached , trustRootCertificate );
991
1015
} catch (Exception e ) {
992
1016
log .warn ("Failed to read or parse cached BLOB." , e );
993
1017
return null ;
@@ -1044,18 +1068,27 @@ private MetadataBLOB parseAndVerifyBlob(ByteArray jwt, X509Certificate trustRoot
1044
1068
InvalidKeyException ,
1045
1069
Base64UrlException ,
1046
1070
FidoMetadataDownloaderException {
1047
- Scanner s = new Scanner (new ByteArrayInputStream (jwt .getBytes ())).useDelimiter ("\\ ." );
1048
- final ByteArray header = ByteArray .fromBase64Url (s .next ());
1049
- final ByteArray payload = ByteArray .fromBase64Url (s .next ());
1050
- final ByteArray signature = ByteArray .fromBase64Url (s .next ());
1051
- return verifyBlob (header , payload , signature , trustRootCertificate );
1071
+ return verifyBlob (parseBlob (jwt ), trustRootCertificate );
1052
1072
}
1053
1073
1054
- private MetadataBLOB verifyBlob (
1055
- ByteArray jwtHeader ,
1056
- ByteArray jwtPayload ,
1057
- ByteArray jwtSignature ,
1058
- X509Certificate trustRootCertificate )
1074
+ private MetadataBLOB parseAndMaybeVerifyBlob (ByteArray jwt , X509Certificate trustRootCertificate )
1075
+ throws CertPathValidatorException ,
1076
+ InvalidAlgorithmParameterException ,
1077
+ CertificateException ,
1078
+ IOException ,
1079
+ NoSuchAlgorithmException ,
1080
+ SignatureException ,
1081
+ InvalidKeyException ,
1082
+ Base64UrlException ,
1083
+ FidoMetadataDownloaderException {
1084
+ if (verifyDownloadsOnly ) {
1085
+ return parseBlob (jwt ).blob ;
1086
+ } else {
1087
+ return verifyBlob (parseBlob (jwt ), trustRootCertificate );
1088
+ }
1089
+ }
1090
+
1091
+ private MetadataBLOB verifyBlob (ParseResult parseResult , X509Certificate trustRootCertificate )
1059
1092
throws IOException ,
1060
1093
CertificateException ,
1061
1094
NoSuchAlgorithmException ,
@@ -1064,12 +1097,7 @@ private MetadataBLOB verifyBlob(
1064
1097
CertPathValidatorException ,
1065
1098
InvalidAlgorithmParameterException ,
1066
1099
FidoMetadataDownloaderException {
1067
- final ObjectMapper headerJsonMapper =
1068
- com .yubico .internal .util .JacksonCodecs .json ()
1069
- .configure (DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES , true )
1070
- .setBase64Variant (Base64Variants .MIME_NO_LINEFEEDS );
1071
- final MetadataBLOBHeader header =
1072
- headerJsonMapper .readValue (jwtHeader .getBytes (), MetadataBLOBHeader .class );
1100
+ final MetadataBLOBHeader header = parseResult .blob .getHeader ();
1073
1101
1074
1102
final List <X509Certificate > certChain ;
1075
1103
if (header .getX5u ().isPresent ()) {
@@ -1117,9 +1145,9 @@ private MetadataBLOB verifyBlob(
1117
1145
1118
1146
signature .initVerify (leafCert .getPublicKey ());
1119
1147
signature .update (
1120
- (jwtHeader .getBase64Url () + "." + jwtPayload .getBase64Url ())
1148
+ (parseResult . jwtHeader .getBase64Url () + "." + parseResult . jwtPayload .getBase64Url ())
1121
1149
.getBytes (StandardCharsets .UTF_8 ));
1122
- if (!signature .verify (jwtSignature .getBytes ())) {
1150
+ if (!signature .verify (parseResult . jwtSignature .getBytes ())) {
1123
1151
throw new FidoMetadataDownloaderException (Reason .BAD_SIGNATURE );
1124
1152
}
1125
1153
@@ -1134,10 +1162,28 @@ private MetadataBLOB verifyBlob(
1134
1162
pathParams .setDate (Date .from (clock .instant ()));
1135
1163
cpv .validate (blobCertPath , pathParams );
1136
1164
1137
- return new MetadataBLOB (
1138
- header ,
1139
- JacksonCodecs .jsonWithDefaultEnums ()
1140
- .readValue (jwtPayload .getBytes (), MetadataBLOBPayload .class ));
1165
+ return parseResult .blob ;
1166
+ }
1167
+
1168
+ private static ParseResult parseBlob (ByteArray jwt ) throws IOException , Base64UrlException {
1169
+ Scanner s = new Scanner (new ByteArrayInputStream (jwt .getBytes ())).useDelimiter ("\\ ." );
1170
+ final ByteArray jwtHeader = ByteArray .fromBase64Url (s .next ());
1171
+ final ByteArray jwtPayload = ByteArray .fromBase64Url (s .next ());
1172
+ final ByteArray jwtSignature = ByteArray .fromBase64Url (s .next ());
1173
+
1174
+ final ObjectMapper headerJsonMapper =
1175
+ com .yubico .internal .util .JacksonCodecs .json ()
1176
+ .configure (DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES , true )
1177
+ .setBase64Variant (Base64Variants .MIME_NO_LINEFEEDS );
1178
+
1179
+ return new ParseResult (
1180
+ new MetadataBLOB (
1181
+ headerJsonMapper .readValue (jwtHeader .getBytes (), MetadataBLOBHeader .class ),
1182
+ JacksonCodecs .jsonWithDefaultEnums ()
1183
+ .readValue (jwtPayload .getBytes (), MetadataBLOBPayload .class )),
1184
+ jwtHeader ,
1185
+ jwtPayload ,
1186
+ jwtSignature );
1141
1187
}
1142
1188
1143
1189
private static ByteArray readAll (InputStream is ) throws IOException {
@@ -1158,4 +1204,12 @@ private static ByteArray verifyHash(ByteArray contents, Set<ByteArray> acceptedC
1158
1204
return null ;
1159
1205
}
1160
1206
}
1207
+
1208
+ @ Value
1209
+ private static class ParseResult {
1210
+ private MetadataBLOB blob ;
1211
+ private ByteArray jwtHeader ;
1212
+ private ByteArray jwtPayload ;
1213
+ private ByteArray jwtSignature ;
1214
+ }
1161
1215
}
0 commit comments